Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sftp additions #4

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
10 changes: 9 additions & 1 deletion data/userdb.txt
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
root:0:123456
root:0:password1
root:0:qwerty
root:0:p4ssw0rd
root:0:administrator
root:0:america
root:0:changeme
www:501:www
www-data:501:www-data

9 changes: 9 additions & 0 deletions kippo.cfg.dist
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ download_path = dl
# (default: 0)
#download_limit_size = 10485760

# Allow the attacker to exit the honeypot on request or try to 'trick' the attacker with another shell.
# note: depending on the attackers client (e.g. putty), will just quit regardless.
#
# (default: false)
exit_jail = false

# sftp_enabled enables the sftp subsystem
sftp_enabled = true

# Directory where virtual file contents are kept in.
#
# This is only used by commands like 'cat' to display the contents of files.
Expand Down
8 changes: 7 additions & 1 deletion kippo/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,15 @@ def call(self):

class command_exit(HoneyPotCommand):
def call(self):
cfg = config()
self.exit_jail = True
if cfg.has_option('honeypot', 'exit_jail'):
if (cfg.get('honeypot', 'exit_jail') == "false"):
self.exit_jail = False
if 'PuTTY' in self.honeypot.clientVersion or \
'libssh' in self.honeypot.clientVersion or \
'sshlib' in self.honeypot.clientVersion:
'sshlib' in self.honeypot.clientVersion or \
self.exit_jail is False:
self.honeypot.terminal.loseConnection()
return
self.honeypot.terminal.reset()
Expand Down
192 changes: 191 additions & 1 deletion kippo/core/fs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) 2009 Upi Tamminen <[email protected]>
# See the COPYRIGHT file for more information

import os, time, fnmatch
import os, time, fnmatch, re, stat, errno
from kippo.core.config import config

A_NAME, \
Expand Down Expand Up @@ -176,5 +176,195 @@ def is_dir(self, path):
if l:
return True
return False
# additions for SFTP support, try to keep functions here similar to os.*

def open(self, filename, openFlags, mode):
#print "fs.open %s" % filename

#if (openFlags & os.O_APPEND == os.O_APPEND):
# print "fs.open append"

#if (openFlags & os.O_CREAT == os.O_CREAT):
# print "fs.open creat"

#if (openFlags & os.O_TRUNC == os.O_TRUNC):
# print "fs.open trunc"

#if (openFlags & os.O_EXCL == os.O_EXCL):
# print "fs.open excl"

if openFlags & os.O_RDWR == os.O_RDWR:
raise notImplementedError

elif openFlags & os.O_WRONLY == os.O_WRONLY:
# ensure we do not save with executable bit set
realmode = mode & ~(stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)

#print "fs.open wronly"
# TODO: safeoutfile could contains source IP address
safeoutfile = '%s/%s_%s' % \
(config().get('honeypot', 'download_path'),
time.strftime('%Y%m%d%H%M%S'),
re.sub('[^A-Za-z0-9]', '_', filename))
#print "fs.open file for writing, saving to %s" % safeoutfile

self.mkfile(filename, 0, 0, 0, stat.S_IFREG | mode)
fd = os.open(safeoutfile, openFlags, realmode)
self.update_realfile(self.getfile(filename), safeoutfile)

return fd

elif openFlags & os.O_RDONLY == os.O_RDONLY:
return None

return None

# FIXME mkdir() name conflicts with existing mkdir
def mkdir2(self, path):
dir = self.getfile(path)
if dir != False:
raise OSError(errno.EEXIST, os.strerror(errno.EEXIST), path)
return self.mkdir(path, 0, 0, 4096, 16877)

def rmdir(self, path):
raise notImplementedError

def utime(self, path, atime, mtime):
p = self.getfile(path)
if p == False:
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
p[A_CTIME] = mtime

def chmod(self, path, perm):
p = self.getfile(path)
if p == False:
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
p[A_MODE] = stat.S_IFMT(p[A_MODE]) | perm

def chown(self, path, uid, gid):
p = self.getfile(path)
if p == False:
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
if (uid != -1):
p[A_UID] = uid
if (gid != -1):
p[A_GID] = gid

def remove(self, path):
p = self.getfile(path)
if p == False:
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
self.get_path(os.path.dirname(path)).remove(p)
return

def readlink(self, path):
p = self.getfile(path)
if p == False:
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
if not (p[A_MODE] & stat.S_IFLNK):
raise OSError
return p[A_TARGET]

def symlink(self, targetPath, linkPath):
raise notImplementedError

def rename(self, oldpath, newpath):
#print "rename %s to %s" % (oldpath, newpath)
old = self.getfile(oldpath)
if old == False:
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
new = self.getfile(newpath)
if new != False:
raise OSError(errno.EEXIST, os.strerror(errno.EEXIST))

self.get_path(os.path.dirname(oldpath)).remove(old)
old[A_NAME] = os.path.basename(newpath)
self.get_path(os.path.dirname(newpath)).append(old)
return

def read(self, fd, size):
# this should not be called, we intercept at readChunk
raise notImplementedError

def write(self, fd, string):
return os.write(fd, string)

def close(self, fd):
if (fd == None):
return True
return os.close(fd)

def lseek(self, fd, offset, whence):
if (fd == None):
return True
return os.lseek(fd, offset, whence)

def listdir(self, path):
names = [x[A_NAME] for x in self.get_path(path)]
return names

def lstat(self, path):

# need to treat / as exception
if (path == "/"):
p = { A_TYPE:T_DIR, A_UID:0, A_GID:0, A_SIZE:4096, A_MODE:16877, A_CTIME:time.time() }
else:
p = self.getfile(path)

if p == False:
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))

return _statobj(
p[A_MODE],
0,
0,
1,
p[A_UID],
p[A_GID],
p[A_SIZE],
p[A_CTIME],
p[A_CTIME],
p[A_CTIME])

def stat(self, path):
if (path == "/"):
p = { A_TYPE:T_DIR, A_UID:0, A_GID:0, A_SIZE:4096, A_MODE:16877, A_CTIME:time.time() }
else:
p = self.getfile(path)

if (p == False):
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))

#if p[A_MODE] & stat.S_IFLNK == stat.S_IFLNK:
if p[A_TYPE] == T_LINK:
return self.stat(p[A_TARGET])

return self.lstat(path)

def realpath(self, path):
return path

def update_size(self, filename, size):
f = self.getfile(filename)
if (f == False):
return
if (f[A_TYPE] != T_FILE):
return
f[A_SIZE] = size


# transform a tuple into a stat object
class _statobj:
def __init__(self, st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime, st_mtime, st_ctime):
self.st_mode = st_mode
self.st_ino = st_ino
self.st_dev = st_dev
self.st_nlink = st_nlink
self.st_uid = st_uid
self.st_gid = st_gid
self.st_size = st_size
self.st_atime = st_atime
self.st_mtime = st_mtime
self.st_ctime = st_ctime

# vim: set sw=4 et:
Loading