This commit is contained in:
Michel Oosterhof
2014-09-25 07:23:43 +00:00
3 changed files with 384 additions and 4 deletions

View File

@ -87,6 +87,9 @@ dsa_private_key = data/ssh_host_dsa_key
# (default: false) # (default: false)
exec_enabled = true exec_enabled = true
# sftp_enabled enables the sftp subsystem
sftp_enabled = true
# IP address to bind to when opening outgoing connections. Used exclusively by # IP address to bind to when opening outgoing connections. Used exclusively by
# the wget command. # the wget command.
# #

View File

@ -1,7 +1,7 @@
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com> # Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information # See the COPYRIGHT file for more information
import os, time, fnmatch import os, time, fnmatch, re, stat, errno
from kippo.core.config import config from kippo.core.config import config
A_NAME, \ A_NAME, \
@ -177,4 +177,195 @@ class HoneyPotFilesystem(object):
return True return True
return False 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: # vim: set sw=4 et:

View File

@ -4,15 +4,20 @@
import twisted import twisted
from twisted.cred import portal from twisted.cred import portal
from twisted.conch import avatar, interfaces as conchinterfaces from twisted.conch import avatar, interfaces as conchinterfaces
from twisted.conch.ssh import factory, userauth, connection, keys, session, transport from twisted.conch.ssh import factory, userauth, connection, keys, session, transport, filetransfer
from twisted.python import log from twisted.conch.ssh.filetransfer import FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL
import twisted.conch.ls
from twisted.python import log, components
from zope.interface import implements from zope.interface import implements
import os import os
import copy
import time import time
import ConfigParser import ConfigParser
from kippo.core import ttylog, utils from kippo.core import ttylog, utils, fs
from kippo.core.config import config from kippo.core.config import config
import kippo.core.auth import kippo.core.auth
import kippo.core.honeypot import kippo.core.honeypot
@ -215,6 +220,11 @@ class HoneyPotAvatar(avatar.ConchUser):
userdb = core.auth.UserDB() userdb = core.auth.UserDB()
self.uid = self.gid = userdb.getUID(self.username) self.uid = self.gid = userdb.getUID(self.username)
# sftp support enabled only when option is explicitly set
if self.env.cfg.has_option('honeypot', 'sftp_enabled'):
if ( self.env.cfg.get('honeypot', 'sftp_enabled') == "true" ):
self.subsystemLookup['sftp'] = filetransfer.FileTransferServer
if not self.uid: if not self.uid:
self.home = '/root' self.home = '/root'
else: else:
@ -304,4 +314,180 @@ def getDSAKeys():
privateKeyString = f.read() privateKeyString = f.read()
return publicKeyString, privateKeyString return publicKeyString, privateKeyString
class KippoSFTPFile:
implements(conchinterfaces.ISFTPFile)
def __init__(self, server, filename, flags, attrs):
self.server = server
self.filename = filename
self.transfer_completed = 0
self.bytes_written = 0
openFlags = 0
if flags & FXF_READ == FXF_READ and flags & FXF_WRITE == 0:
openFlags = os.O_RDONLY
if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == 0:
openFlags = os.O_WRONLY
if flags & FXF_WRITE == FXF_WRITE and flags & FXF_READ == FXF_READ:
openFlags = os.O_RDWR
if flags & FXF_APPEND == FXF_APPEND:
openFlags |= os.O_APPEND
if flags & FXF_CREAT == FXF_CREAT:
openFlags |= os.O_CREAT
if flags & FXF_TRUNC == FXF_TRUNC:
openFlags |= os.O_TRUNC
if flags & FXF_EXCL == FXF_EXCL:
openFlags |= os.O_EXCL
if attrs.has_key("permissions"):
mode = attrs["permissions"]
del attrs["permissions"]
else:
mode = 0777
fd = server.fs.open(filename, openFlags, mode)
if attrs:
self.server.setAttrs(filename, attrs)
self.fd = fd
# cache a copy of file in memory to read from in readChunk
if flags & FXF_READ == FXF_READ:
self.contents = self.server.fs.file_contents(self.filename)
def close(self):
if ( self.bytes_written > 0 ):
self.server.fs.update_size(self.filename, self.bytes_written)
return self.server.fs.close(self.fd)
def readChunk(self, offset, length):
return self.contents[offset:offset+length]
def writeChunk(self, offset, data):
self.server.fs.lseek(self.fd, offset, os.SEEK_SET)
self.server.fs.write(self.fd, data)
self.bytes_written += len(data)
def getAttrs(self):
s = self.server.fs.fstat(self.fd)
return self.server._getAttrs(s)
def setAttrs(self, attrs):
raise NotImplementedError
class KippoSFTPDirectory:
def __init__(self, server, directory):
self.server = server
self.files = server.fs.listdir(directory)
self.dir = directory
def __iter__(self):
return self
def next(self):
try:
f = self.files.pop(0)
except IndexError:
raise StopIteration
else:
s = self.server.fs.lstat(os.path.join(self.dir, f))
longname = twisted.conch.ls.lsLine(f, s)
attrs = self.server._getAttrs(s)
return (f, longname, attrs)
def close(self):
self.files = []
class KippoSFTPServer:
implements(conchinterfaces.ISFTPServer)
def __init__(self, avatar):
self.avatar = avatar
# FIXME we should not copy fs here, but do this at avatar instantiation
self.fs = fs.HoneyPotFilesystem(copy.deepcopy(self.avatar.env.fs))
def _absPath(self, path):
home = self.avatar.home
return os.path.abspath(os.path.join(home, path))
def _setAttrs(self, path, attrs):
if attrs.has_key("uid") and attrs.has_key("gid"):
self.fs.chown(path, attrs["uid"], attrs["gid"])
if attrs.has_key("permissions"):
self.fs.chmod(path, attrs["permissions"])
if attrs.has_key("atime") and attrs.has_key("mtime"):
self.fs.utime(path, attrs["atime"], attrs["mtime"])
def _getAttrs(self, s):
return {
"size" : s.st_size,
"uid" : s.st_uid,
"gid" : s.st_gid,
"permissions" : s.st_mode,
"atime" : int(s.st_atime),
"mtime" : int(s.st_mtime)
}
def gotVersion(self, otherVersion, extData):
return {}
def openFile(self, filename, flags, attrs):
print "SFTP openFile: %s" % filename
return KippoSFTPFile(self, self._absPath(filename), flags, attrs)
def removeFile(self, filename):
print "SFTP removeFile: %s" % filename
return self.fs.remove(self._absPath(filename))
def renameFile(self, oldpath, newpath):
print "SFTP renameFile: %s %s" % (oldpath, newpath)
return self.fs.rename(self._absPath(oldpath), self._absPath(newpath))
def makeDirectory(self, path, attrs):
print "SFTP makeDirectory: %s" % path
path = self._absPath(path)
self.fs.mkdir2(path)
self._setAttrs(path, attrs)
return
def removeDirectory(self, path):
print "SFTP removeDirectory: %s" % path
return self.fs.rmdir(self._absPath(path))
def openDirectory(self, path):
print "SFTP OpenDirectory: %s" % path
return KippoSFTPDirectory(self, self._absPath(path))
def getAttrs(self, path, followLinks):
print "SFTP getAttrs: %s" % path
path = self._absPath(path)
if followLinks:
s = self.fs.stat(path)
else:
s = self.fs.lstat(path)
return self._getAttrs(s)
def setAttrs(self, path, attrs):
print "SFTP setAttrs: %s" % path
path = self._absPath(path)
return self._setAttrs(path, attrs)
def readLink(self, path):
print "SFTP readLink: %s" % path
path = self._absPath(path)
return self.fs.readlink(path)
def makeLink(self, linkPath, targetPath):
print "SFTP makeLink: %s" % path
linkPath = self._absPath(linkPath)
targetPath = self._absPath(targetPath)
return self.fs.symlink(targetPath, linkPath)
def realPath(self, path):
print "SFTP realPath: %s" % path
return self.fs.realpath(self._absPath(path))
def extendedRequest(self, extName, extData):
raise NotImplementedError
components.registerAdapter( KippoSFTPServer, HoneyPotAvatar, conchinterfaces.ISFTPServer)
# vim: set et sw=4 et: # vim: set et sw=4 et: