diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f7a579..73d69dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ +* 2015-12-28 Interact port (default 5123) only listens on loopback interface now (127.0.0.1) +* 2015-12-24 Redirect to file (>) now works for most commands and is logged in dl/ directory * 2015-12-06 UID information is now retrieved from honeyfs/etc/passwd. If you added additional users you will need to add these to the passwd file as well * 2015-12-04 New 'free' command with '-h' and '-m' options diff --git a/cowrie/commands/__init__.py b/cowrie/commands/__init__.py index da471a2..1555bb0 100644 --- a/cowrie/commands/__init__.py +++ b/cowrie/commands/__init__.py @@ -7,7 +7,6 @@ __all__ = [ 'base', 'busybox', 'curl', - 'dice', 'env', 'ethtool', 'free', @@ -17,7 +16,6 @@ __all__ = [ 'iptables', 'last', 'ls', - 'malware', 'netstat', 'nohup', 'ping', diff --git a/cowrie/commands/tar.py b/cowrie/commands/tar.py index ded6be5..996e741 100644 --- a/cowrie/commands/tar.py +++ b/cowrie/commands/tar.py @@ -9,22 +9,9 @@ from twisted.python import log from cowrie.core.honeypot import HoneyPotCommand from cowrie.core.fs import * -from cowrie.commands import dice, malware commands = {} -def pick_handler(cmd, size): - """ - """ - if size in malware.slist: - handler = malware.slist[size] - elif cmd in malware.clist: - handler = malware.clist[cmd] - else: - handler = random.choice(dice.clist) - return handler - - class command_tar(HoneyPotCommand): """ @@ -92,8 +79,6 @@ class command_tar(HoneyPotCommand): elif f.isfile(): self.mkfullpath(os.path.dirname(dest), f) self.fs.mkfile(dest, 0, 0, f.size, f.mode, f.mtime) - self.protocol.commands[dest] = \ - pick_handler(os.path.basename(dest), f.size) else: log.msg( 'tar: skipping [%s]' % f.name ) diff --git a/cowrie/commands/which.py b/cowrie/commands/which.py index a921077..8efc6cf 100644 --- a/cowrie/commands/which.py +++ b/cowrie/commands/which.py @@ -25,4 +25,4 @@ class command_which(HoneyPotCommand): continue # Definition -commands['/bin/which'] = command_which +commands['which'] = command_which diff --git a/cowrie/core/auth.py b/cowrie/core/auth.py index 2ffc38e..00645f4 100644 --- a/cowrie/core/auth.py +++ b/cowrie/core/auth.py @@ -219,4 +219,3 @@ class AuthRandom(object): self.savevars() return auth -# vim: set sw=4 et: diff --git a/cowrie/core/avatar.py b/cowrie/core/avatar.py new file mode 100644 index 0000000..b26091c --- /dev/null +++ b/cowrie/core/avatar.py @@ -0,0 +1,63 @@ +# Copyright (c) 2009-2014 Upi Tamminen +# See the COPYRIGHT file for more information + +""" +This module contains ... +""" + +from zope.interface import implementer + +import twisted +from twisted.conch import avatar +from twisted.conch.interfaces import IConchUser, ISession, ISFTPServer +from twisted.conch.ssh import filetransfer as conchfiletransfer +from twisted.python import log, components + +from cowrie.core import pwd +from cowrie.ssh import session +from cowrie.ssh import filetransfer +from cowrie.ssh import forwarding + + +@implementer(IConchUser) +class CowrieUser(avatar.ConchUser): + """ + """ + + def __init__(self, username, server): + avatar.ConchUser.__init__(self) + self.username = username + self.server = server + self.cfg = self.server.cfg + + self.channelLookup.update( + {"session": session.HoneyPotSSHSession, + "direct-tcpip": forwarding.CowrieOpenConnectForwardingClient}) + + try: + pwentry = pwd.Passwd(self.cfg).getpwnam(self.username) + self.uid = pwentry["pw_uid"] + self.gid = pwentry["pw_gid"] + self.home = pwentry["pw_dir"] + except: + self.uid = 1001 + self.gid = 1001 + self.home = '/home' + + # Sftp support enabled only when option is explicitly set + try: + if (self.cfg.get('honeypot', 'sftp_enabled') == "true"): + self.subsystemLookup['sftp'] = conchfiletransfer.FileTransferServer + except: + pass + + + def logout(self): + """ + """ + log.msg('avatar {} logging out'.format(self.username)) + + +components.registerAdapter(filetransfer.SFTPServerForCowrieUser, CowrieUser, ISFTPServer) +components.registerAdapter(session.SSHSessionForCowrieUser, CowrieUser, ISession) + diff --git a/cowrie/core/checkers.py b/cowrie/core/checkers.py index 3a054a8..b3bfa9b 100644 --- a/cowrie/core/checkers.py +++ b/cowrie/core/checkers.py @@ -130,4 +130,3 @@ class HoneypotPasswordChecker: username=theusername, password=thepassword) return False -# vim: set sw=4 et: diff --git a/cowrie/core/config.py b/cowrie/core/config.py index 8709476..44ff0b6 100644 --- a/cowrie/core/config.py +++ b/cowrie/core/config.py @@ -12,4 +12,3 @@ def readConfigFile(cfgfile): cfg.readfp(open(cfgfile)) return cfg -# vim: set sw=4 et: diff --git a/cowrie/core/credentials.py b/cowrie/core/credentials.py index 4a79f6b..286f019 100644 --- a/cowrie/core/credentials.py +++ b/cowrie/core/credentials.py @@ -100,4 +100,3 @@ class UsernamePasswordIP: self.password = password self.ip = ip -# vim: set sw=4 et: diff --git a/cowrie/core/dblog.py b/cowrie/core/dblog.py index 4812543..b59d7b9 100644 --- a/cowrie/core/dblog.py +++ b/cowrie/core/dblog.py @@ -195,4 +195,3 @@ class DBLogger(object): def handleFileDownload(self, session, args): pass -# vim: set sw=4 et: diff --git a/cowrie/core/fs.py b/cowrie/core/fs.py index 204c3d2..0af959c 100644 --- a/cowrie/core/fs.py +++ b/cowrie/core/fs.py @@ -42,6 +42,7 @@ class TooManyLevels(Exception): class FileNotFound(Exception): """ + raise OSError(errno.ENOENT, os.strerror(errno.ENOENT)) """ pass @@ -561,4 +562,3 @@ class _statobj: self.st_mtime = st_mtime self.st_ctime = st_ctime -# vim: set sw=4 et: diff --git a/cowrie/core/honeypot.py b/cowrie/core/honeypot.py index 51a11f9..8abf202 100644 --- a/cowrie/core/honeypot.py +++ b/cowrie/core/honeypot.py @@ -31,7 +31,6 @@ class HoneyPotCommand(object): # MS-DOS style redirect handling, inside the command if '>' in self.args: self.writtenBytes = 0 - self.writeln = self.writeToFileLn self.write = self.writeToFile index = self.args.index(">") @@ -45,7 +44,6 @@ class HoneyPotCommand(object): self.fs.update_realfile(self.fs.getfile(self.outfile), self.safeoutfile) else: self.write = self.protocol.terminal.write - self.writeln = self.protocol.writeln def writeToFile(self, data): @@ -57,12 +55,6 @@ class HoneyPotCommand(object): self.fs.update_size(self.outfile, self.writtenBytes) - def writeToFileLn(self, data): - """ - """ - self.writeToFile(data+'\n') - - def start(self): """ """ @@ -130,10 +122,11 @@ class HoneyPotShell(object): def __init__(self, protocol, interactive=True): self.protocol = protocol self.interactive = interactive - self.showPrompt() self.cmdpending = [] self.environ = protocol.environ + self.showPrompt() + def lineReceived(self, line): """ @@ -260,6 +253,7 @@ class HoneyPotShell(object): attrs = {'path': path} self.protocol.terminal.write(prompt % attrs) + self.protocol.ps = (prompt % attrs , '> ') def handle_CTRL_C(self): diff --git a/cowrie/core/interact.py b/cowrie/core/interact.py index 83f1b4e..ffe0ce1 100644 --- a/cowrie/core/interact.py +++ b/cowrie/core/interact.py @@ -206,4 +206,3 @@ def makeInteractFactory(honeypotFactory): ifactory.honeypotFactory = honeypotFactory return ifactory -# vim: set sw=4 et: diff --git a/cowrie/core/keys.py b/cowrie/core/keys.py index a6f619c..105b39a 100644 --- a/cowrie/core/keys.py +++ b/cowrie/core/keys.py @@ -61,4 +61,3 @@ def getDSAKeys(cfg): privateKeyString = f.read() return publicKeyString, privateKeyString - diff --git a/cowrie/core/output.py b/cowrie/core/output.py index f4d3133..609314d 100644 --- a/cowrie/core/output.py +++ b/cowrie/core/output.py @@ -169,4 +169,3 @@ class Output(object): del self.sessions[sessionno] del self.ips[sessionno] -# vim: set sw=4 et: diff --git a/cowrie/core/protocol.py b/cowrie/core/protocol.py index aa1a4f4..02cfd38 100644 --- a/cowrie/core/protocol.py +++ b/cowrie/core/protocol.py @@ -91,7 +91,7 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol, TimeoutMixin): """ this logs out when connection times out """ - self.write( 'timed out waiting for input: auto-logout\n' ) + self.terminal.write( 'timed out waiting for input: auto-logout\n' ) ret = failure.Failure(error.ProcessTerminated(exitCode=1)) self.terminal.transport.processEnded(ret) @@ -150,12 +150,15 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol, TimeoutMixin): if self.fs.exists(i): path = i break + txt = os.path.normpath('%s/%s' % \ (self.cfg.get('honeypot', 'txtcmds_path'), path)) if os.path.exists(txt) and os.path.isfile(txt): return self.txtcmd(txt) + if path in self.commands: return self.commands[path] + return None @@ -167,14 +170,6 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol, TimeoutMixin): self.cmdstack[-1].lineReceived(line) - def writeln(self, data): - """ - Sometimes still called after disconnect because of a deferred - """ - if self.terminal: - self.terminal.write(data+'\n') - - def call_command(self, cmd, *args): """ """ @@ -246,6 +241,7 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin '\x06': self.handle_RIGHT, # CTRL-F '\x09': self.handle_TAB, '\x0B': self.handle_CTRL_K, # CTRL-K + '\x0C': self.handle_CTRL_L, # CTRL-L '\x0E': self.handle_DOWN, # CTRL-N '\x10': self.handle_UP, # CTRL-P '\x15': self.handle_CTRL_U, # CTRL-U @@ -256,7 +252,7 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin """ """ try: - self.write(self.fs.file_contents('/etc/motd')+'\n') + self.terminal.write(self.fs.file_contents('/etc/motd')+'\n') except: pass @@ -348,6 +344,16 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin self.lineBuffer = self.lineBuffer[0:self.lineBufferIndex] + def handle_CTRL_L(self): + """ + Handle a 'form feed' byte - generally used to request a screen + refresh/redraw. + """ + self.terminal.eraseDisplay() + self.terminal.cursorHome() + self.drawInputLine() + + def handle_CTRL_U(self): """ """ @@ -357,152 +363,3 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin self.lineBuffer = self.lineBuffer[self.lineBufferIndex:] self.lineBufferIndex = 0 - - -class LoggingServerProtocol(insults.ServerProtocol): - """ - Wrapper for ServerProtocol that implements TTY logging - """ - - def __init__(self, prot=None, *a, **kw): - insults.ServerProtocol.__init__(self, prot, *a, **kw) - self.cfg = a[0].cfg - self.bytesReceived = 0 - self.interactors = [] - - try: - self.bytesReceivedLimit = int(self.cfg.get('honeypot', 'download_limit_size')) - except: - self.bytesReceivedLimit = 0 - - if prot is HoneyPotExecProtocol: - self.type = 'e' # execcmd - else: - self.type = 'i' # interactive - - - def connectionMade(self): - """ - """ - transportId = self.transport.session.conn.transport.transportId - channelId = self.transport.session.id - - self.ttylog_file = '%s/tty/%s-%s-%s%s.log' % \ - (self.cfg.get('honeypot', 'log_path'), - time.strftime('%Y%m%d-%H%M%S'), transportId, channelId, - self.type) - ttylog.ttylog_open(self.ttylog_file, time.time()) - self.ttylog_open = True - - log.msg(eventid='COW0004', ttylog=self.ttylog_file, - format='Opening TTY Log: %(ttylog)s') - - self.stdinlog_file = '%s/%s-%s-%s-stdin.log' % \ - (self.cfg.get('honeypot', 'download_path'), - time.strftime('%Y%m%d-%H%M%S'), transportId, channelId) - self.stdinlog_open = False - - insults.ServerProtocol.connectionMade(self) - - - def write(self, bytes): - """ - """ - for i in self.interactors: - i.sessionWrite(bytes) - - if self.ttylog_open: - ttylog.ttylog_write(self.ttylog_file, len(bytes), - ttylog.TYPE_OUTPUT, time.time(), bytes) - - insults.ServerProtocol.write(self, bytes) - - - def dataReceived(self, data): - """ - """ - self.bytesReceived += len(data) - if self.bytesReceivedLimit and self.bytesReceived > self.bytesReceivedLimit: - log.msg(eventid='COW0015', format='Data upload limit reached') - #self.loseConnection() - self.eofReceived() - return - - if self.stdinlog_open: - with file(self.stdinlog_file, 'ab') as f: - f.write(data) - elif self.ttylog_open: - ttylog.ttylog_write(self.ttylog_file, len(data), - ttylog.TYPE_INPUT, time.time(), data) - - insults.ServerProtocol.dataReceived(self, data) - - - def eofReceived(self): - """ - """ - if self.terminalProtocol: - self.terminalProtocol.eofReceived() - - - def addInteractor(self, interactor): - """ - """ - self.interactors.append(interactor) - - - def delInteractor(self, interactor): - """ - """ - self.interactors.remove(interactor) - - - def loseConnection(self): - """ - override super to remove the terminal reset on logout - - """ - self.transport.loseConnection() - - - def connectionLost(self, reason): - """ - FIXME: this method is called 4 times on logout.... - it's called once from Avatar.closed() if disconnected - """ - log.msg("received call to LSP.connectionLost") - - for i in self.interactors: - i.sessionClosed() - - transport = self.transport.session.conn.transport - - if self.stdinlog_open: - try: - with open(self.stdinlog_file, 'rb') as f: - shasum = hashlib.sha256(f.read()).hexdigest() - shasumfile = self.cfg.get('honeypot', - 'download_path') + "/" + shasum - if (os.path.exists(shasumfile)): - os.remove(self.stdinlog_file) - else: - os.rename(self.stdinlog_file, shasumfile) - os.symlink(shasum, self.stdinlog_file) - log.msg(eventid='COW0007', - format='Saved stdin contents to %(outfile)s', - url='stdin', outfile=shasumfile, shasum=shasum) - except IOError as e: - pass - finally: - self.stdinlog_open = False - - if self.ttylog_open: - log.msg(eventid='COW0012', format='Closing TTY Log: %(ttylog)s', - ttylog=self.ttylog_file) - ttylog.ttylog_close(self.ttylog_file, time.time()) - self.ttylog_open = False - - self.cfg = None - insults.ServerProtocol.connectionLost(self, reason) - -# vim: set sw=4 et: diff --git a/cowrie/core/pwd.py b/cowrie/core/pwd.py index ef136c7..7053109 100644 --- a/cowrie/core/pwd.py +++ b/cowrie/core/pwd.py @@ -190,4 +190,3 @@ class Group(object): return _ raise KeyError("getgruid(): uid not found in group file: " + uid) -# vim: set sw=4 et: diff --git a/cowrie/core/realm.py b/cowrie/core/realm.py index f0b5cb4..768c71e 100644 --- a/cowrie/core/realm.py +++ b/cowrie/core/realm.py @@ -38,7 +38,7 @@ from twisted.python import log from cowrie.core import protocol from cowrie.core import server -from cowrie.core import ssh +from cowrie.core import avatar import sys import gc @@ -71,8 +71,7 @@ class HoneyPotRealm: if conchinterfaces.IConchUser in interfaces: return interfaces[0], \ - ssh.CowrieUser(avatarId, server.CowrieServer(self.cfg)), lambda:None + avatar.CowrieUser(avatarId, server.CowrieServer(self.cfg)), lambda:None else: raise Exception("No supported interfaces found.") - diff --git a/cowrie/core/ttylog.py b/cowrie/core/ttylog.py index 97b5bd4..92ee9d2 100644 --- a/cowrie/core/ttylog.py +++ b/cowrie/core/ttylog.py @@ -38,4 +38,3 @@ def ttylog_close(logfile, stamp): sec, usec = int(stamp), int(1000000 * (stamp - int(stamp))) f.write(struct.pack(' +# See the COPYRIGHT file for more information + +""" +This module contains ... +""" + +import os +import time +import hashlib + +from twisted.python import log +from twisted.conch.insults import insults + +from cowrie.core import ttylog +from cowrie.core import protocol + + +class LoggingServerProtocol(insults.ServerProtocol): + """ + Wrapper for ServerProtocol that implements TTY logging + """ + + def __init__(self, prot=None, *a, **kw): + insults.ServerProtocol.__init__(self, prot, *a, **kw) + self.cfg = a[0].cfg + self.bytesReceived = 0 + self.interactors = [] + + try: + self.bytesReceivedLimit = int(self.cfg.get('honeypot', 'download_limit_size')) + except: + self.bytesReceivedLimit = 0 + + if prot is protocol.HoneyPotExecProtocol: + self.type = 'e' # execcmd + else: + self.type = 'i' # interactive + + + def connectionMade(self): + """ + """ + transportId = self.transport.session.conn.transport.transportId + channelId = self.transport.session.id + + self.ttylog_file = '%s/tty/%s-%s-%s%s.log' % \ + (self.cfg.get('honeypot', 'log_path'), + time.strftime('%Y%m%d-%H%M%S'), transportId, channelId, + self.type) + ttylog.ttylog_open(self.ttylog_file, time.time()) + self.ttylog_open = True + + log.msg(eventid='COW0004', ttylog=self.ttylog_file, + format='Opening TTY Log: %(ttylog)s') + + self.stdinlog_file = '%s/%s-%s-%s-stdin.log' % \ + (self.cfg.get('honeypot', 'download_path'), + time.strftime('%Y%m%d-%H%M%S'), transportId, channelId) + self.stdinlog_open = False + + insults.ServerProtocol.connectionMade(self) + + + def write(self, bytes): + """ + """ + for i in self.interactors: + i.sessionWrite(bytes) + + if self.ttylog_open: + ttylog.ttylog_write(self.ttylog_file, len(bytes), + ttylog.TYPE_OUTPUT, time.time(), bytes) + + insults.ServerProtocol.write(self, bytes) + + + def dataReceived(self, data): + """ + """ + self.bytesReceived += len(data) + if self.bytesReceivedLimit and self.bytesReceived > self.bytesReceivedLimit: + log.msg(eventid='COW0015', format='Data upload limit reached') + #self.loseConnection() + self.eofReceived() + return + + if self.stdinlog_open: + with file(self.stdinlog_file, 'ab') as f: + f.write(data) + elif self.ttylog_open: + ttylog.ttylog_write(self.ttylog_file, len(data), + ttylog.TYPE_INPUT, time.time(), data) + + insults.ServerProtocol.dataReceived(self, data) + + + def eofReceived(self): + """ + """ + if self.terminalProtocol: + self.terminalProtocol.eofReceived() + + + def addInteractor(self, interactor): + """ + """ + self.interactors.append(interactor) + + + def delInteractor(self, interactor): + """ + """ + self.interactors.remove(interactor) + + + def loseConnection(self): + """ + override super to remove the terminal reset on logout + + """ + self.transport.loseConnection() + + + def connectionLost(self, reason): + """ + FIXME: this method is called 4 times on logout.... + it's called once from Avatar.closed() if disconnected + """ + log.msg("received call to LSP.connectionLost") + + for i in self.interactors: + i.sessionClosed() + + transport = self.transport.session.conn.transport + + if self.stdinlog_open: + try: + with open(self.stdinlog_file, 'rb') as f: + shasum = hashlib.sha256(f.read()).hexdigest() + shasumfile = self.cfg.get('honeypot', + 'download_path') + "/" + shasum + if (os.path.exists(shasumfile)): + os.remove(self.stdinlog_file) + else: + os.rename(self.stdinlog_file, shasumfile) + os.symlink(shasum, self.stdinlog_file) + log.msg(eventid='COW0007', + format='Saved stdin contents to %(outfile)s', + url='stdin', outfile=shasumfile, shasum=shasum) + except IOError as e: + pass + finally: + self.stdinlog_open = False + + if self.ttylog_open: + log.msg(eventid='COW0012', format='Closing TTY Log: %(ttylog)s', + ttylog=self.ttylog_file) + ttylog.ttylog_close(self.ttylog_file, time.time()) + self.ttylog_open = False + + self.cfg = None + insults.ServerProtocol.connectionLost(self, reason) + diff --git a/cowrie/ssh/__init__.py b/cowrie/ssh/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cowrie/core/connection.py b/cowrie/ssh/connection.py similarity index 100% rename from cowrie/core/connection.py rename to cowrie/ssh/connection.py diff --git a/cowrie/core/ssh.py b/cowrie/ssh/filetransfer.py similarity index 51% rename from cowrie/core/ssh.py rename to cowrie/ssh/filetransfer.py index f363427..6335252 100644 --- a/cowrie/core/ssh.py +++ b/cowrie/ssh/filetransfer.py @@ -10,209 +10,14 @@ import os from zope.interface import implementer import twisted -from twisted.conch import avatar, interfaces as conchinterfaces -from twisted.conch.ssh import session +from twisted.conch.interfaces import ISFTPFile, ISFTPServer from twisted.conch.ssh import filetransfer -from twisted.conch.ssh import forwarding 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 twisted.conch.ssh.common import getNS - -from cowrie.core import pwd -from cowrie.core import protocol +from twisted.python import log - -class HoneyPotSSHSession(session.SSHSession): - """ - This is an SSH channel that's used for SSH sessions - """ - - def __init__(self, *args, **kw): - session.SSHSession.__init__(self, *args, **kw) - #self.__dict__['request_auth_agent_req@openssh.com'] = self.request_agent - - - def request_env(self, data): - """ - """ - name, rest = getNS(data) - value, rest = getNS(rest) - if rest: - raise ValueError("Bad data given in env request") - log.msg(eventid='COW0013', format='request_env: %(name)s=%(value)s', - name=name, value=value) - # Environment variables come after shell or before exec command - if self.session: - self.session.environ[name] = value - return 0 - - - def request_agent(self, data): - """ - """ - log.msg('request_agent: %s' % (repr(data),)) - return 0 - - - def request_x11_req(self, data): - """ - """ - log.msg('request_x11: %s' % (repr(data),)) - return 0 - - - def closed(self): - """ - This is reliably called on session close/disconnect and calls the avatar - """ - session.SSHSession.closed(self) - - - def sendEOF(self): - """ - Utility function to request to send EOF for this session - """ - self.conn.sendEOF(self) - - - def sendClose(self): - """ - Utility function to request to send close for this session - """ - self.conn.sendClose(self) - - - def channelClosed(self): - """ - """ - log.msg("Called channelClosed in SSHSession") - - - -@implementer(conchinterfaces.IConchUser) -class CowrieUser(avatar.ConchUser): - """ - """ - - def __init__(self, username, server): - avatar.ConchUser.__init__(self) - self.username = username - self.server = server - self.cfg = self.server.cfg - - self.channelLookup.update( - {"session": HoneyPotSSHSession, - "direct-tcpip": CowrieOpenConnectForwardingClient}) - - try: - pwentry = pwd.Passwd(self.cfg).getpwnam(self.username) - self.uid = pwentry["pw_uid"] - self.gid = pwentry["pw_gid"] - self.home = pwentry["pw_dir"] - except: - self.uid = 1001 - self.gid = 1001 - self.home = '/home' - - # Sftp support enabled only when option is explicitly set - try: - if (self.cfg.get('honeypot', 'sftp_enabled') == "true"): - self.subsystemLookup['sftp'] = filetransfer.FileTransferServer - except: - pass - - - def logout(self): - """ - """ - log.msg('avatar {} logging out'.format(self.username)) - - - -@implementer(conchinterfaces.ISession) -class SSHSessionForCowrieUser: - """ - """ - - def __init__(self, avatar, reactor=None): - """ - Construct an C{SSHSessionForCowrieUser}. - - @param avatar: The L{CowrieUser} for whom this is an SSH session. - @param reactor: An L{IReactorProcess} used to handle shell and exec - requests. Uses the default reactor if None. - """ - self.protocol = None - self.avatar = avatar - self.server = avatar.server - self.cfg = avatar.cfg - self.uid = avatar.uid - self.gid = avatar.gid - self.username = avatar.username - self.environ = { - 'LOGNAME': self.username, - 'USER': self.username, - 'HOME': self.avatar.home} - if self.uid==0: - self.environ['PATH']='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' - else: - self.environ['PATH']='/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games' - - def openShell(self, processprotocol): - """ - """ - self.protocol = protocol.LoggingServerProtocol( - protocol.HoneyPotInteractiveProtocol, self) - self.protocol.makeConnection(processprotocol) - processprotocol.makeConnection(session.wrapProtocol(self.protocol)) - - - def getPty(self, terminal, windowSize, attrs): - """ - """ - self.environ['TERM'] = terminal - log.msg(eventid='COW0010', width=windowSize[0], height=windowSize[1], - format='Terminal Size: %(width)s %(height)s') - self.windowSize = windowSize - return None - - - def execCommand(self, processprotocol, cmd): - """ - """ - self.protocol = protocol.LoggingServerProtocol( - protocol.HoneyPotExecProtocol, self, cmd) - self.protocol.makeConnection(processprotocol) - processprotocol.makeConnection(session.wrapProtocol(self.protocol)) - - - def closed(self): - """ - this is reliably called on both logout and disconnect - we notify the protocol here we lost the connection - """ - if self.protocol: - self.protocol.connectionLost("disconnected") - self.protocol = None - - - def eofReceived(self): - """ - """ - if self.protocol: - self.protocol.eofReceived() - - - def windowChanged(self, windowSize): - """ - """ - self.windowSize = windowSize - - - -@implementer(conchinterfaces.ISFTPFile) +@implementer(ISFTPFile) class CowrieSFTPFile: """ """ @@ -335,7 +140,7 @@ class CowrieSFTPDirectory: -@implementer(conchinterfaces.ISFTPServer) +@implementer(ISFTPServer) class SFTPServerForCowrieUser: """ """ @@ -476,39 +281,3 @@ class SFTPServerForCowrieUser: """ raise NotImplementedError - - -components.registerAdapter(SFTPServerForCowrieUser, CowrieUser, conchinterfaces.ISFTPServer) -components.registerAdapter(SSHSessionForCowrieUser, CowrieUser, session.ISession) - - -def CowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar): - """ - """ - remoteHP, origHP = twisted.conch.ssh.forwarding.unpackOpen_direct_tcpip(data) - log.msg(eventid='COW0014', format='direct-tcp connection request to %(dst_ip)s:%(dst_port)s', - dst_ip=remoteHP[0], dst_port=remoteHP[1]) - return CowrieConnectForwardingChannel(remoteHP, - remoteWindow=remoteWindow, remoteMaxPacket=remoteMaxPacket, - avatar=avatar) - - - -class CowrieConnectForwardingChannel(forwarding.SSHConnectForwardingChannel): - """ - """ - def channelOpen(self, specificData): - """ - """ - pass - - - def dataReceived(self, data): - """ - """ - log.msg(eventid='COW0015', - format='direct-tcp forward to %(dst_ip)s:%(dst_port)s with data %(data)s', - dst_ip=self.hostport[0], dst_port=self.hostport[1], data=repr(data)) - self._close("Connection refused") - -# vim: set et sw=4 et: diff --git a/cowrie/ssh/forwarding.py b/cowrie/ssh/forwarding.py new file mode 100644 index 0000000..e00921b --- /dev/null +++ b/cowrie/ssh/forwarding.py @@ -0,0 +1,41 @@ +# Copyright (c) 2009-2014 Upi Tamminen +# See the COPYRIGHT file for more information + +""" +This module contains ... +""" + +import twisted +from twisted.conch.ssh import forwarding +from twisted.python import log + + +def CowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar): + """ + """ + remoteHP, origHP = twisted.conch.ssh.forwarding.unpackOpen_direct_tcpip(data) + log.msg(eventid='COW0014', format='direct-tcp connection request to %(dst_ip)s:%(dst_port)s', + dst_ip=remoteHP[0], dst_port=remoteHP[1]) + return CowrieConnectForwardingChannel(remoteHP, + remoteWindow=remoteWindow, remoteMaxPacket=remoteMaxPacket, + avatar=avatar) + + + +class CowrieConnectForwardingChannel(forwarding.SSHConnectForwardingChannel): + """ + """ + def channelOpen(self, specificData): + """ + """ + pass + + + def dataReceived(self, data): + """ + """ + log.msg(eventid='COW0015', + format='direct-tcp forward to %(dst_ip)s:%(dst_port)s with data %(data)s', + dst_ip=self.hostport[0], dst_port=self.hostport[1], data=repr(data)) + self._close("Connection refused") + diff --git a/cowrie/ssh/session.py b/cowrie/ssh/session.py new file mode 100644 index 0000000..d30b85f --- /dev/null +++ b/cowrie/ssh/session.py @@ -0,0 +1,169 @@ +# Copyright (c) 2009-2014 Upi Tamminen +# See the COPYRIGHT file for more information + +""" +This module contains ... +""" + +import os + +from zope.interface import implementer + +import twisted +from twisted.conch.interfaces import ISession +from twisted.conch.ssh import session +from twisted.python import log +from twisted.conch.ssh.common import getNS + +from cowrie.core import protocol +from cowrie.core import pwd +from cowrie.insults import insults + + +class HoneyPotSSHSession(session.SSHSession): + """ + This is an SSH channel that's used for SSH sessions + """ + + def __init__(self, *args, **kw): + session.SSHSession.__init__(self, *args, **kw) + #self.__dict__['request_auth_agent_req@openssh.com'] = self.request_agent + + + def request_env(self, data): + """ + """ + name, rest = getNS(data) + value, rest = getNS(rest) + if rest: + raise ValueError("Bad data given in env request") + log.msg(eventid='COW0013', format='request_env: %(name)s=%(value)s', + name=name, value=value) + # FIXME: This only works for shell, not for exec command + if self.session: + self.session.environ[name] = value + return 0 + + + def request_agent(self, data): + """ + """ + log.msg('request_agent: %s' % (repr(data),)) + return 0 + + + def request_x11_req(self, data): + """ + """ + log.msg('request_x11: %s' % (repr(data),)) + return 0 + + + def closed(self): + """ + This is reliably called on session close/disconnect and calls the avatar + """ + session.SSHSession.closed(self) + + + def sendEOF(self): + """ + Utility function to request to send EOF for this session + """ + self.conn.sendEOF(self) + + + def sendClose(self): + """ + Utility function to request to send close for this session + """ + self.conn.sendClose(self) + + + def channelClosed(self): + """ + """ + log.msg("Called channelClosed in SSHSession") + + + +@implementer(ISession) +class SSHSessionForCowrieUser: + """ + """ + + def __init__(self, avatar, reactor=None): + """ + Construct an C{SSHSessionForCowrieUser}. + + @param avatar: The L{CowrieUser} for whom this is an SSH session. + @param reactor: An L{IReactorProcess} used to handle shell and exec + requests. Uses the default reactor if None. + """ + self.protocol = None + self.avatar = avatar + self.server = avatar.server + self.cfg = avatar.cfg + self.uid = avatar.uid + self.gid = avatar.gid + self.username = avatar.username + self.environ = { + 'LOGNAME': self.username, + 'USER': self.username, + 'HOME': self.avatar.home, + 'TMOUT': '1800'} + if self.uid==0: + self.environ['PATH']='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' + else: + self.environ['PATH']='/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games' + + def openShell(self, processprotocol): + """ + """ + self.protocol = insults.LoggingServerProtocol( + protocol.HoneyPotInteractiveProtocol, self) + self.protocol.makeConnection(processprotocol) + processprotocol.makeConnection(session.wrapProtocol(self.protocol)) + + + def getPty(self, terminal, windowSize, attrs): + """ + """ + self.environ['TERM'] = terminal + log.msg(eventid='COW0010', width=windowSize[0], height=windowSize[1], + format='Terminal Size: %(width)s %(height)s') + self.windowSize = windowSize + return None + + + def execCommand(self, processprotocol, cmd): + """ + """ + self.protocol = insults.LoggingServerProtocol( + protocol.HoneyPotExecProtocol, self, cmd) + self.protocol.makeConnection(processprotocol) + processprotocol.makeConnection(session.wrapProtocol(self.protocol)) + + + def closed(self): + """ + this is reliably called on both logout and disconnect + we notify the protocol here we lost the connection + """ + if self.protocol: + self.protocol.connectionLost("disconnected") + self.protocol = None + + + def eofReceived(self): + """ + """ + if self.protocol: + self.protocol.eofReceived() + + + def windowChanged(self, windowSize): + """ + """ + self.windowSize = windowSize + diff --git a/cowrie/core/transport.py b/cowrie/ssh/transport.py similarity index 99% rename from cowrie/core/transport.py rename to cowrie/ssh/transport.py index 8ad90ac..253b04e 100644 --- a/cowrie/core/transport.py +++ b/cowrie/ssh/transport.py @@ -17,8 +17,8 @@ from twisted.conch.openssh_compat import primes from twisted.conch.ssh.common import getNS from twisted.protocols.policies import TimeoutMixin -from cowrie.core import connection -from cowrie.core import userauth +from cowrie.ssh import connection +from cowrie.ssh import userauth from cowrie.core import keys as cowriekeys @@ -291,4 +291,3 @@ class HoneyPotTransport(transport.SSHServerTransport, TimeoutMixin): % (reason, desc)) self.transport.loseConnection() - diff --git a/cowrie/core/userauth.py b/cowrie/ssh/userauth.py similarity index 100% rename from cowrie/core/userauth.py rename to cowrie/ssh/userauth.py diff --git a/twisted/plugins/cowrie_plugin.py b/twisted/plugins/cowrie_plugin.py index a2a3b2b..75e8865 100644 --- a/twisted/plugins/cowrie_plugin.py +++ b/twisted/plugins/cowrie_plugin.py @@ -45,10 +45,11 @@ from twisted.cred import portal from cowrie.core.config import readConfigFile from cowrie import core -import cowrie.core.transport import cowrie.core.realm import cowrie.core.checkers +import cowrie.ssh.transport + class Options(usage.Options): """ FIXME: Docstring @@ -80,6 +81,25 @@ class CowrieServiceMaker(object): cfg = readConfigFile(options["config"]) + top_service = service.MultiService() + application = service.Application('cowrie') + top_service.setServiceParent(application) + + factory = cowrie.ssh.transport.HoneyPotSSHFactory(cfg) + + factory.portal = portal.Portal(core.realm.HoneyPotRealm(cfg)) + factory.portal.registerChecker( + core.checkers.HoneypotPublicKeyChecker()) + factory.portal.registerChecker( + core.checkers.HoneypotPasswordChecker(cfg)) + + if cfg.has_option('honeypot', 'auth_none_enabled') and \ + cfg.get('honeypot', 'auth_none_enabled').lower() in \ + ('yes', 'true', 'on'): + factory.portal.registerChecker( + core.checkers.HoneypotNoneChecker()) + + if cfg.has_option('honeypot', 'listen_addr'): listen_addr = cfg.get('honeypot', 'listen_addr') else: @@ -93,23 +113,9 @@ class CowrieServiceMaker(object): else: listen_port = 2222 - factory = core.transport.HoneyPotSSHFactory(cfg) - factory.portal = portal.Portal(core.realm.HoneyPotRealm(cfg)) - factory.portal.registerChecker( - core.checkers.HoneypotPublicKeyChecker()) - factory.portal.registerChecker( - core.checkers.HoneypotPasswordChecker(cfg)) - - if cfg.has_option('honeypot', 'auth_none_enabled') and \ - cfg.get('honeypot', 'auth_none_enabled').lower() in \ - ('yes', 'true', 'on'): - factory.portal.registerChecker( - core.checkers.HoneypotNoneChecker()) - - top_service = top_service = service.MultiService() - for i in listen_addr.split(): svc = internet.TCPServer(listen_port, factory, interface=i) + # FIXME: Use addService on top_service ? svc.setServiceParent(top_service) if cfg.has_option('honeypot', 'interact_enabled') and \ @@ -118,11 +124,10 @@ class CowrieServiceMaker(object): iport = int(cfg.get('honeypot', 'interact_port')) from cowrie.core import interact svc = internet.TCPServer(iport, - interact.makeInteractFactory(factory)) + interact.makeInteractFactory(factory), interface='127.0.0.1') + # FIXME: Use addService on top_service ? svc.setServiceParent(top_service) - application = service.Application('cowrie') - top_service.setServiceParent(application) return top_service # Now construct an object which *provides* the relevant interfaces