From 1df9bf04b5e537a8ffc7a011e35748cc7930f639 Mon Sep 17 00:00:00 2001 From: Dave Germiquet Date: Sat, 26 Dec 2015 23:30:04 -0500 Subject: [PATCH 01/19] Fixed up bugs, made it return back JSON when dealing with XMPP and sending alerts to room. Room Configuration looks like this: Where cowrie is the room, conference is the server configuration and user is the user logging in [database_xmpp] server = conference.domain.net user = cowrie@domain.net password = xxxxxxx muc = cowrie signal_createsession = cowrie-events signal_connectionlost = cowrie-events signal_loginfailed = cowrie-events signal_loginsucceeded = cowrie-events signal_command = cowrie-events signal_clientversion = cowrie-events debug=true --- cowrie/dblog/xmpp.py | 58 +++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/cowrie/dblog/xmpp.py b/cowrie/dblog/xmpp.py index 667119e..9d4466b 100644 --- a/cowrie/dblog/xmpp.py +++ b/cowrie/dblog/xmpp.py @@ -1,27 +1,30 @@ from twisted.words.xish import domish from twisted.python import log from wokkel.xmppim import AvailablePresence +from twisted.words.protocols.jabber.jid import JID from wokkel import muc import uuid +import json class XMPPLoggerProtocol(muc.MUCClient): - def __init__(self, server, rooms, nick): + def __init__(self, rooms, server, nick): muc.MUCClient.__init__(self) - self.server = server - self.jrooms = rooms + self.server = rooms.host + self.jrooms = rooms + self._roomOccupantMap = {} + log.msg(rooms.user) + log.msg(rooms.host) self.nick = nick self.last = {} self.activity = None - def initialized(self): + def connectionInitialized(self): """The bot has connected to the xmpp server, now try to join the room. """ - for i in self.jrooms: - print(i) - self.join(self.server, i, self.nick).addCallback(self.initRoom) + self.join(self.jrooms, self.nick); - def initRoom(self, room): + def joinedRoom(self, room): log.msg( 'Joined room %s' % room.name ) def connectionMade(self): @@ -31,7 +34,7 @@ class XMPPLoggerProtocol(muc.MUCClient): self.send(AvailablePresence()) def connectionLost(self, reason): - logmsg( 'Disconnected!' ) + log.msg( 'Disconnected!' ) def onMessage(self, msg): pass @@ -69,10 +72,11 @@ class DBLogger(dblog.DBLogger): for i in range(8)]) jid = user + '/' + resource application = service.Application('honeypot') - self.run(application, jid, password, muc, channels) + self.run(application, jid, password, JID(None,[muc,server,None]), channels) def run(self, application, jidstr, password, muc, channels, anon=True): - self.xmppclient = XMPPClient(jid.JID(jidstr), password) + + self.xmppclient = XMPPClient(JID(jidstr), password) if self.cfg.has_option('database_xmpp', 'debug') and \ self.cfg.get('database_xmpp', 'debug') in ('1', 'true', 'yes'): self.xmppclient.logTraffic = True # DEBUG HERE @@ -94,18 +98,16 @@ class DBLogger(dblog.DBLogger): (self.signals[msgtype], self.muc.server) , msg) def report(self, msgtype, to, xmsg): - body = domish.Element((None, 'body')) - body.addContent('\n') - msg = domish.Element(('http://github.com/micheloosterhof/cowrie', 'cowrie')) + msg = {} msg['type'] = msgtype - msg.addChild(xmsg) - body.addChild(msg) - self.muc.groupChat(jid.JID(to), body) + msg['message'] = xmsg + msgJson = json.dumps(msg,indent=5) + self.muc.groupChat(self.muc.jrooms, msgJson) # We have to return an unique ID def createSession(self, peerIP, peerPort, hostIP, hostPort): session = uuid.uuid4().hex - ses = domish.Element((None, 'session')) + ses = {} ses['session'] = session ses['remote_host'] = peerIP ses['remote_port'] = str(peerPort) @@ -122,50 +124,50 @@ class DBLogger(dblog.DBLogger): pass def handleConnectionLost(self, session, args): - ses = domish.Element((None, 'session')) + ses = {} ses['session'] = session self.broadcast('connectionlost', ses) def handleLoginFailed(self, session, args): - ses = domish.Element((None, 'credentials')) + ses = {} ses['session'] = session ses['username'] = args['username'] ses['password'] = args['password'] self.broadcast('loginfailed', ses) def handleLoginSucceeded(self, session, args): - ses = domish.Element((None, 'credentials')) + ses = {} ses['session'] = session ses['username'] = args['username'] ses['password'] = args['password'] self.broadcast('loginsucceeded', ses) def handleCommand(self, session, args): - ses = domish.Element((None, 'command')) + ses = {} ses['session'] = session ses['command'] = 'known' - ses.addContent(args['input']) + ses['input'] = args['input'] self.broadcast('command', ses) def handleUnknownCommand(self, session, args): - ses = domish.Element((None, 'command')) + ses = {} ses['session'] = session ses['command'] = 'unknown' - ses.addContent(args['input']) + ses['input'] = args['input'] self.broadcast('command', ses) def handleInput(self, session, args): - ses = domish.Element((None, 'input')) + ses = {} ses['session'] = session ses['realm'] = args['realm'] - ses.addContent(args['input']) + ses['input'] = args['input'] self.broadcast('input', ses) def handleTerminalSize(self, session, args): pass def handleClientVersion(self, session, args): - ses = domish.Element((None, 'version')) + ses = {} ses['session'] = session ses['version'] = args['version'] self.broadcast('clientversion', ses) From f961139550673ba6ca6d387c0f42cf5e9527c5cc Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 12:56:55 +0400 Subject: [PATCH 02/19] split off avatar --- cowrie/core/avatar.py | 56 +++++++++++++++++++++++++++++++++++++++++++ cowrie/core/realm.py | 3 ++- cowrie/core/ssh.py | 40 ------------------------------- 3 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 cowrie/core/avatar.py diff --git a/cowrie/core/avatar.py b/cowrie/core/avatar.py new file mode 100644 index 0000000..e80358f --- /dev/null +++ b/cowrie/core/avatar.py @@ -0,0 +1,56 @@ +# 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 +from twisted.conch.ssh import filetransfer +from twisted.python import log + +from cowrie.core import pwd + + +@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": 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)) + diff --git a/cowrie/core/realm.py b/cowrie/core/realm.py index f0b5cb4..942efa0 100644 --- a/cowrie/core/realm.py +++ b/cowrie/core/realm.py @@ -39,6 +39,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,7 +72,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/ssh.py b/cowrie/core/ssh.py index f363427..87eccab 100644 --- a/cowrie/core/ssh.py +++ b/cowrie/core/ssh.py @@ -91,46 +91,6 @@ class HoneyPotSSHSession(session.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: """ From 7b32a6709053310e6972fe56b5bf13c749c29e2a Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 13:05:46 +0400 Subject: [PATCH 03/19] working avatar splitoff --- cowrie/core/avatar.py | 14 ++++++++++---- cowrie/core/ssh.py | 15 +++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/cowrie/core/avatar.py b/cowrie/core/avatar.py index e80358f..3a7a649 100644 --- a/cowrie/core/avatar.py +++ b/cowrie/core/avatar.py @@ -9,11 +9,12 @@ from zope.interface import implementer import twisted from twisted.conch import avatar -from twisted.conch.interfaces import IConchUser +from twisted.conch.interfaces import IConchUser, ISession, ISFTPServer from twisted.conch.ssh import filetransfer -from twisted.python import log +from twisted.python import log, components from cowrie.core import pwd +from cowrie.core import ssh @implementer(IConchUser) @@ -28,8 +29,8 @@ class CowrieUser(avatar.ConchUser): self.cfg = self.server.cfg self.channelLookup.update( - {"session": HoneyPotSSHSession, - "direct-tcpip": CowrieOpenConnectForwardingClient}) + {"session": ssh.HoneyPotSSHSession, + "direct-tcpip": ssh.CowrieOpenConnectForwardingClient}) try: pwentry = pwd.Passwd(self.cfg).getpwnam(self.username) @@ -54,3 +55,8 @@ class CowrieUser(avatar.ConchUser): """ log.msg('avatar {} logging out'.format(self.username)) + +components.registerAdapter(ssh.SFTPServerForCowrieUser, CowrieUser, ISFTPServer) +components.registerAdapter(ssh.SSHSessionForCowrieUser, CowrieUser, ISession) + + diff --git a/cowrie/core/ssh.py b/cowrie/core/ssh.py index 87eccab..f3cee67 100644 --- a/cowrie/core/ssh.py +++ b/cowrie/core/ssh.py @@ -10,13 +10,13 @@ import os from zope.interface import implementer import twisted -from twisted.conch import avatar, interfaces as conchinterfaces +from twisted.conch.interfaces import ISFTPFile, ISFTPServer, ISession from twisted.conch.ssh import session 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.python import log from twisted.conch.ssh.common import getNS from cowrie.core import pwd @@ -91,7 +91,7 @@ class HoneyPotSSHSession(session.SSHSession): -@implementer(conchinterfaces.ISession) +@implementer(ISession) class SSHSessionForCowrieUser: """ """ @@ -172,7 +172,7 @@ class SSHSessionForCowrieUser: -@implementer(conchinterfaces.ISFTPFile) +@implementer(ISFTPFile) class CowrieSFTPFile: """ """ @@ -295,7 +295,7 @@ class CowrieSFTPDirectory: -@implementer(conchinterfaces.ISFTPServer) +@implementer(ISFTPServer) class SFTPServerForCowrieUser: """ """ @@ -437,11 +437,6 @@ class SFTPServerForCowrieUser: raise NotImplementedError - -components.registerAdapter(SFTPServerForCowrieUser, CowrieUser, conchinterfaces.ISFTPServer) -components.registerAdapter(SSHSessionForCowrieUser, CowrieUser, session.ISession) - - def CowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar): """ """ From 5bda4dfdec07742e1d6ca3c0a7794fa1df12e8e8 Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 13:18:47 +0400 Subject: [PATCH 04/19] split off session.py --- cowrie/core/avatar.py | 5 +- cowrie/core/session.py | 167 +++++++++++++++++++++++++++++++++++++++++ cowrie/core/ssh.py | 156 +------------------------------------- 3 files changed, 171 insertions(+), 157 deletions(-) create mode 100644 cowrie/core/session.py diff --git a/cowrie/core/avatar.py b/cowrie/core/avatar.py index 3a7a649..403d59b 100644 --- a/cowrie/core/avatar.py +++ b/cowrie/core/avatar.py @@ -15,6 +15,7 @@ from twisted.python import log, components from cowrie.core import pwd from cowrie.core import ssh +from cowrie.core import session @implementer(IConchUser) @@ -29,7 +30,7 @@ class CowrieUser(avatar.ConchUser): self.cfg = self.server.cfg self.channelLookup.update( - {"session": ssh.HoneyPotSSHSession, + {"session": session.HoneyPotSSHSession, "direct-tcpip": ssh.CowrieOpenConnectForwardingClient}) try: @@ -57,6 +58,6 @@ class CowrieUser(avatar.ConchUser): components.registerAdapter(ssh.SFTPServerForCowrieUser, CowrieUser, ISFTPServer) -components.registerAdapter(ssh.SSHSessionForCowrieUser, CowrieUser, ISession) +components.registerAdapter(session.SSHSessionForCowrieUser, CowrieUser, ISession) diff --git a/cowrie/core/session.py b/cowrie/core/session.py new file mode 100644 index 0000000..6eca0b5 --- /dev/null +++ b/cowrie/core/session.py @@ -0,0 +1,167 @@ +# 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 pwd +from cowrie.core import protocol + + +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(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 + diff --git a/cowrie/core/ssh.py b/cowrie/core/ssh.py index f3cee67..45c2bfd 100644 --- a/cowrie/core/ssh.py +++ b/cowrie/core/ssh.py @@ -10,166 +10,12 @@ import os from zope.interface import implementer import twisted -from twisted.conch.interfaces import ISFTPFile, ISFTPServer, ISession -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 -from twisted.conch.ssh.common import getNS - -from cowrie.core import pwd -from cowrie.core import protocol - - - -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(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(ISFTPFile) From 4fd704ecaffd97f1ddb761028f35f90facb700a7 Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 13:25:35 +0400 Subject: [PATCH 05/19] complete split of ssh.py --- cowrie/core/avatar.py | 11 ++++--- cowrie/core/{ssh.py => filetransfer.py} | 32 ------------------- cowrie/core/forwarding.py | 41 +++++++++++++++++++++++++ cowrie/core/realm.py | 1 - 4 files changed, 47 insertions(+), 38 deletions(-) rename cowrie/core/{ssh.py => filetransfer.py} (87%) create mode 100644 cowrie/core/forwarding.py diff --git a/cowrie/core/avatar.py b/cowrie/core/avatar.py index 403d59b..3eab3c3 100644 --- a/cowrie/core/avatar.py +++ b/cowrie/core/avatar.py @@ -10,12 +10,13 @@ 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 +from twisted.conch.ssh import filetransfer as conchfiletransfer from twisted.python import log, components from cowrie.core import pwd -from cowrie.core import ssh from cowrie.core import session +from cowrie.core import filetransfer +from cowrie.core import forwarding @implementer(IConchUser) @@ -31,7 +32,7 @@ class CowrieUser(avatar.ConchUser): self.channelLookup.update( {"session": session.HoneyPotSSHSession, - "direct-tcpip": ssh.CowrieOpenConnectForwardingClient}) + "direct-tcpip": forwarding.CowrieOpenConnectForwardingClient}) try: pwentry = pwd.Passwd(self.cfg).getpwnam(self.username) @@ -46,7 +47,7 @@ class CowrieUser(avatar.ConchUser): # Sftp support enabled only when option is explicitly set try: if (self.cfg.get('honeypot', 'sftp_enabled') == "true"): - self.subsystemLookup['sftp'] = filetransfer.FileTransferServer + self.subsystemLookup['sftp'] = conchfiletransfer.FileTransferServer except: pass @@ -57,7 +58,7 @@ class CowrieUser(avatar.ConchUser): log.msg('avatar {} logging out'.format(self.username)) -components.registerAdapter(ssh.SFTPServerForCowrieUser, CowrieUser, ISFTPServer) +components.registerAdapter(filetransfer.SFTPServerForCowrieUser, CowrieUser, ISFTPServer) components.registerAdapter(session.SSHSessionForCowrieUser, CowrieUser, ISession) diff --git a/cowrie/core/ssh.py b/cowrie/core/filetransfer.py similarity index 87% rename from cowrie/core/ssh.py rename to cowrie/core/filetransfer.py index 45c2bfd..6335252 100644 --- a/cowrie/core/ssh.py +++ b/cowrie/core/filetransfer.py @@ -12,7 +12,6 @@ from zope.interface import implementer import twisted 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 @@ -282,34 +281,3 @@ class SFTPServerForCowrieUser: """ raise NotImplementedError - -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/core/forwarding.py b/cowrie/core/forwarding.py new file mode 100644 index 0000000..e00921b --- /dev/null +++ b/cowrie/core/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/core/realm.py b/cowrie/core/realm.py index 942efa0..1e04f89 100644 --- a/cowrie/core/realm.py +++ b/cowrie/core/realm.py @@ -38,7 +38,6 @@ 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 From 79e5e81bc3635b850aafcde9c01e6ad112f6670d Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 13:30:22 +0400 Subject: [PATCH 06/19] remove vim trailer --- cowrie/core/auth.py | 1 - cowrie/core/avatar.py | 1 - cowrie/core/checkers.py | 1 - cowrie/core/config.py | 1 - cowrie/core/credentials.py | 1 - cowrie/core/dblog.py | 1 - cowrie/core/fs.py | 1 - cowrie/core/interact.py | 1 - cowrie/core/keys.py | 1 - cowrie/core/output.py | 1 - cowrie/core/protocol.py | 1 - cowrie/core/pwd.py | 1 - cowrie/core/realm.py | 1 - cowrie/core/transport.py | 1 - cowrie/core/ttylog.py | 1 - cowrie/core/utils.py | 2 -- 16 files changed, 17 deletions(-) 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 index 3eab3c3..fa3cfdf 100644 --- a/cowrie/core/avatar.py +++ b/cowrie/core/avatar.py @@ -61,4 +61,3 @@ class CowrieUser(avatar.ConchUser): 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..1f6e364 100644 --- a/cowrie/core/fs.py +++ b/cowrie/core/fs.py @@ -561,4 +561,3 @@ class _statobj: self.st_mtime = st_mtime self.st_ctime = st_ctime -# vim: set sw=4 et: 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..1172699 100644 --- a/cowrie/core/protocol.py +++ b/cowrie/core/protocol.py @@ -505,4 +505,3 @@ class LoggingServerProtocol(insults.ServerProtocol): 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 1e04f89..768c71e 100644 --- a/cowrie/core/realm.py +++ b/cowrie/core/realm.py @@ -75,4 +75,3 @@ class HoneyPotRealm: else: raise Exception("No supported interfaces found.") - diff --git a/cowrie/core/transport.py b/cowrie/core/transport.py index 8ad90ac..cc3c92b 100644 --- a/cowrie/core/transport.py +++ b/cowrie/core/transport.py @@ -291,4 +291,3 @@ class HoneyPotTransport(transport.SSHServerTransport, TimeoutMixin): % (reason, desc)) self.transport.loseConnection() - 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(' Date: Mon, 28 Dec 2015 14:07:36 +0400 Subject: [PATCH 07/19] add fixme --- cowrie/core/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cowrie/core/session.py b/cowrie/core/session.py index 6eca0b5..bc60478 100644 --- a/cowrie/core/session.py +++ b/cowrie/core/session.py @@ -38,7 +38,7 @@ class HoneyPotSSHSession(session.SSHSession): 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 + # FIXME: This only works for shell, not for exec command if self.session: self.session.environ[name] = value return 0 From e56f4134179cb85b75362e0d10e3b1ddffe9cf4f Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 15:14:57 +0400 Subject: [PATCH 08/19] move ssh files to ssh directory --- cowrie/core/avatar.py | 6 +++--- cowrie/core/fs.py | 1 + cowrie/ssh/__init__.py | 0 cowrie/{core => ssh}/connection.py | 0 cowrie/{core => ssh}/filetransfer.py | 0 cowrie/{core => ssh}/forwarding.py | 0 cowrie/{core => ssh}/session.py | 0 cowrie/{core => ssh}/transport.py | 4 ++-- cowrie/{core => ssh}/userauth.py | 0 twisted/plugins/cowrie_plugin.py | 5 +++-- 10 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 cowrie/ssh/__init__.py rename cowrie/{core => ssh}/connection.py (100%) rename cowrie/{core => ssh}/filetransfer.py (100%) rename cowrie/{core => ssh}/forwarding.py (100%) rename cowrie/{core => ssh}/session.py (100%) rename cowrie/{core => ssh}/transport.py (99%) rename cowrie/{core => ssh}/userauth.py (100%) diff --git a/cowrie/core/avatar.py b/cowrie/core/avatar.py index fa3cfdf..b26091c 100644 --- a/cowrie/core/avatar.py +++ b/cowrie/core/avatar.py @@ -14,9 +14,9 @@ from twisted.conch.ssh import filetransfer as conchfiletransfer from twisted.python import log, components from cowrie.core import pwd -from cowrie.core import session -from cowrie.core import filetransfer -from cowrie.core import forwarding +from cowrie.ssh import session +from cowrie.ssh import filetransfer +from cowrie.ssh import forwarding @implementer(IConchUser) diff --git a/cowrie/core/fs.py b/cowrie/core/fs.py index 1f6e364..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 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/filetransfer.py b/cowrie/ssh/filetransfer.py similarity index 100% rename from cowrie/core/filetransfer.py rename to cowrie/ssh/filetransfer.py diff --git a/cowrie/core/forwarding.py b/cowrie/ssh/forwarding.py similarity index 100% rename from cowrie/core/forwarding.py rename to cowrie/ssh/forwarding.py diff --git a/cowrie/core/session.py b/cowrie/ssh/session.py similarity index 100% rename from cowrie/core/session.py rename to cowrie/ssh/session.py 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 cc3c92b..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 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..4a97972 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 @@ -93,7 +94,7 @@ class CowrieServiceMaker(object): else: listen_port = 2222 - factory = core.transport.HoneyPotSSHFactory(cfg) + factory = cowrie.ssh.transport.HoneyPotSSHFactory(cfg) factory.portal = portal.Portal(core.realm.HoneyPotRealm(cfg)) factory.portal.registerChecker( core.checkers.HoneypotPublicKeyChecker()) From 0a2a3b1e2de61f623f5fe6eb419a43883fee1495 Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 15:46:37 +0400 Subject: [PATCH 09/19] move ServerProtocol to insults --- cowrie/core/protocol.py | 148 --------------------------------- cowrie/insults/__init__.py | 0 cowrie/insults/insults.py | 164 +++++++++++++++++++++++++++++++++++++ cowrie/ssh/session.py | 7 +- 4 files changed, 168 insertions(+), 151 deletions(-) create mode 100644 cowrie/insults/__init__.py create mode 100644 cowrie/insults/insults.py diff --git a/cowrie/core/protocol.py b/cowrie/core/protocol.py index 1172699..34d7aad 100644 --- a/cowrie/core/protocol.py +++ b/cowrie/core/protocol.py @@ -357,151 +357,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) - diff --git a/cowrie/insults/__init__.py b/cowrie/insults/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cowrie/insults/insults.py b/cowrie/insults/insults.py new file mode 100644 index 0000000..8cd0870 --- /dev/null +++ b/cowrie/insults/insults.py @@ -0,0 +1,164 @@ +# Copyright (c) 2009-2014 Upi Tamminen +# 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/session.py b/cowrie/ssh/session.py index bc60478..814a604 100644 --- a/cowrie/ssh/session.py +++ b/cowrie/ssh/session.py @@ -15,8 +15,9 @@ from twisted.conch.ssh import session from twisted.python import log from twisted.conch.ssh.common import getNS -from cowrie.core import pwd from cowrie.core import protocol +from cowrie.core import pwd +from cowrie.insults import insults class HoneyPotSSHSession(session.SSHSession): @@ -118,7 +119,7 @@ class SSHSessionForCowrieUser: def openShell(self, processprotocol): """ """ - self.protocol = protocol.LoggingServerProtocol( + self.protocol = insults.LoggingServerProtocol( protocol.HoneyPotInteractiveProtocol, self) self.protocol.makeConnection(processprotocol) processprotocol.makeConnection(session.wrapProtocol(self.protocol)) @@ -137,7 +138,7 @@ class SSHSessionForCowrieUser: def execCommand(self, processprotocol, cmd): """ """ - self.protocol = protocol.LoggingServerProtocol( + self.protocol = insults.LoggingServerProtocol( protocol.HoneyPotExecProtocol, self, cmd) self.protocol.makeConnection(processprotocol) processprotocol.makeConnection(session.wrapProtocol(self.protocol)) From ec47edd8d0b6ba5874e17b758203a2431a1dfe71 Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 16:02:41 +0400 Subject: [PATCH 10/19] which is both in /usr/bin and /bin --- cowrie/commands/which.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 82b33cf8073ede29021f4d2177586f4ca728521d Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 17:53:31 +0400 Subject: [PATCH 11/19] add CTRL-L (clear screen, redraw prompt) --- cowrie/core/honeypot.py | 4 +++- cowrie/core/protocol.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cowrie/core/honeypot.py b/cowrie/core/honeypot.py index 2ee11db..dd8b339 100644 --- a/cowrie/core/honeypot.py +++ b/cowrie/core/honeypot.py @@ -127,10 +127,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): """ @@ -257,6 +258,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/protocol.py b/cowrie/core/protocol.py index 34d7aad..c71f71b 100644 --- a/cowrie/core/protocol.py +++ b/cowrie/core/protocol.py @@ -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 @@ -246,6 +249,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 @@ -348,6 +352,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): """ """ From 1e566483ec3c7040171c7496d4301e2860037ecf Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 18:06:28 +0400 Subject: [PATCH 12/19] fix motd again --- cowrie/core/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cowrie/core/protocol.py b/cowrie/core/protocol.py index c71f71b..4826600 100644 --- a/cowrie/core/protocol.py +++ b/cowrie/core/protocol.py @@ -260,7 +260,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 From 911ca267930c7889aeaea9609bf1c3168a2d1d50 Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 18:13:50 +0400 Subject: [PATCH 13/19] remove writeln() and variants --- cowrie/core/honeypot.py | 8 -------- cowrie/core/protocol.py | 8 -------- 2 files changed, 16 deletions(-) diff --git a/cowrie/core/honeypot.py b/cowrie/core/honeypot.py index dd8b339..3257f28 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): """ """ diff --git a/cowrie/core/protocol.py b/cowrie/core/protocol.py index 4826600..e667213 100644 --- a/cowrie/core/protocol.py +++ b/cowrie/core/protocol.py @@ -170,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): """ """ From e68a8dca39368fdcfaec8e4ba504e446760194e5 Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 18:21:59 +0400 Subject: [PATCH 14/19] remove malware, dice --- cowrie/commands/__init__.py | 2 -- 1 file changed, 2 deletions(-) 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', From 1cb8ab1f95a1cce51a54826b496a5649f3bccc89 Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 18:23:38 +0400 Subject: [PATCH 15/19] don't create fake malware files --- cowrie/commands/tar.py | 15 --------------- 1 file changed, 15 deletions(-) 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 ) From ceb96d0487d6e8d4870b1ac10ba5b891b9c7a09b Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 18:24:03 +0400 Subject: [PATCH 16/19] set TMOUT --- cowrie/ssh/session.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cowrie/ssh/session.py b/cowrie/ssh/session.py index 814a604..d30b85f 100644 --- a/cowrie/ssh/session.py +++ b/cowrie/ssh/session.py @@ -110,7 +110,8 @@ class SSHSessionForCowrieUser: self.environ = { 'LOGNAME': self.username, 'USER': self.username, - 'HOME': self.avatar.home} + '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: From ecb8976b2018b974cb577a5ab42279d66281bc2c Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 18:42:06 +0400 Subject: [PATCH 17/19] interact now only listens on localhost interface --- twisted/plugins/cowrie_plugin.py | 40 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/twisted/plugins/cowrie_plugin.py b/twisted/plugins/cowrie_plugin.py index 4a97972..75e8865 100644 --- a/twisted/plugins/cowrie_plugin.py +++ b/twisted/plugins/cowrie_plugin.py @@ -81,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: @@ -94,23 +113,9 @@ class CowrieServiceMaker(object): else: listen_port = 2222 - 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()) - - 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 \ @@ -119,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 From 74b46f4bd9bf3cfa93ab5ea69d4decd2b2594d0d Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 18:44:09 +0400 Subject: [PATCH 18/19] cHANGELOG updates --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) 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 From 9519cb3566f3cbd5771301098b0a56ef0e9d4459 Mon Sep 17 00:00:00 2001 From: Michel Oosterhof Date: Mon, 28 Dec 2015 21:47:30 +0400 Subject: [PATCH 19/19] fix write for timeout --- cowrie/core/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cowrie/core/protocol.py b/cowrie/core/protocol.py index e667213..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)