mirror of
https://github.com/cowrie/cowrie.git
synced 2025-07-01 18:07:27 -04:00
Merge branch 'sftp-support' of https://github.com/micheloosterhof/kippo
This commit is contained in:
@ -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.
|
||||||
#
|
#
|
||||||
|
|||||||
193
kippo/core/fs.py
193
kippo/core/fs.py
@ -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:
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user