From f8429dfd0f496b4fd85faa9aa03c7395042640c7 Mon Sep 17 00:00:00 2001 From: desaster Date: Fri, 21 Oct 2011 09:29:06 +0000 Subject: [PATCH] Added a telnet based session management interface for interacting with active sessions git-svn-id: https://kippo.googlecode.com/svn/trunk@209 951d7100-d841-11de-b865-b3884708a8e2 --- kippo.cfg.dist | 10 +++ kippo.tac | 9 +++ kippo/core/honeypot.py | 17 +++++ kippo/core/interact.py | 153 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 kippo/core/interact.py diff --git a/kippo.cfg.dist b/kippo.cfg.dist index ff4e011..edacef9 100644 --- a/kippo.cfg.dist +++ b/kippo.cfg.dist @@ -108,6 +108,16 @@ private_key = private.key # (default: not specified) #banner_file = +# Session management interface. +# +# This is a telnet based service that can be used to interact with active +# sessions. Disabled by default. +# +# (default: false) +interact_enable = false +# (default: 5123) +interact_port = 5123 + # MySQL logging module # # Database structure for this module is supplied in doc/sql/mysql.sql diff --git a/kippo.tac b/kippo.tac index 0fe6983..bde5eb4 100644 --- a/kippo.tac +++ b/kippo.tac @@ -46,4 +46,13 @@ for i in ssh_addr.split(): interface=i) service.setServiceParent(application) +if cfg.has_option('honeypot', 'interact_enabled') and \ + cfg.get('honeypot', 'interact_enabled').lower() in \ + ('yes', 'true', 'on'): + iport = int(cfg.get('honeypot', 'interact_port')) + from kippo.core import interact + from twisted.internet import protocol + service = internet.TCPServer(iport, interact.makeInteractFactory(factory)) + service.setServiceParent(application) + # vim: set ft=python sw=4 et: diff --git a/kippo/core/honeypot.py b/kippo/core/honeypot.py index de80454..f3c7f40 100644 --- a/kippo/core/honeypot.py +++ b/kippo/core/honeypot.py @@ -254,6 +254,7 @@ class HoneyPotProtocol(recvline.HistoricRecvLine): self.cmdstack = [HoneyPotShell(self)] transport = self.terminal.transport.session.conn.transport + transport.factory.sessions.append(self) # this is for the interactors # You are in a maze of twisty little passages, all alike p = transport.transport.getPeer() @@ -296,6 +297,8 @@ class HoneyPotProtocol(recvline.HistoricRecvLine): def connectionLost(self, reason): recvline.HistoricRecvLine.connectionLost(self, reason) + transport = self.terminal.transport.session.conn.transport + transport.factory.sessions.remove(self) self.lastlogExit() # not sure why i need to do this: @@ -392,6 +395,12 @@ class HoneyPotProtocol(recvline.HistoricRecvLine): def handle_TAB(self): self.cmdstack[-1].handle_TAB() + def addInteractor(self, interactor): + self.terminal.interactors.append(interactor) + + def delInteractor(self, interactor): + self.terminal.interactors.remove(interactor) + class LoggingServerProtocol(insults.ServerProtocol): def connectionMade(self): self.ttylog_file = '%s/tty/%s-%s.log' % \ @@ -401,15 +410,20 @@ class LoggingServerProtocol(insults.ServerProtocol): print 'Opening TTY log: %s' % self.ttylog_file ttylog.ttylog_open(self.ttylog_file, time.time()) self.ttylog_open = True + self.interactors = [] insults.ServerProtocol.connectionMade(self) def write(self, bytes, noLog = False): + for i in self.interactors: + i.sessionWrite(bytes) if self.ttylog_open and not noLog: ttylog.ttylog_write(self.ttylog_file, len(bytes), ttylog.DIR_WRITE, time.time(), bytes) insults.ServerProtocol.write(self, bytes) def connectionLost(self, reason): + for i in self.interactors: + i.sessionClosed() if self.ttylog_open: ttylog.ttylog_close(self.ttylog_file, time.time()) self.ttylog_open = False @@ -532,6 +546,9 @@ class HoneyPotSSHFactory(factory.SSHFactory): def __init__(self): cfg = config() + # protocol instances are kept here for use by the interact feature + self.sessions = [] + # convert old pass.db root passwords passdb_file = '%s/pass.db' % (cfg.get('honeypot', 'data_path'),) if os.path.exists(passdb_file): diff --git a/kippo/core/interact.py b/kippo/core/interact.py new file mode 100644 index 0000000..d6132dd --- /dev/null +++ b/kippo/core/interact.py @@ -0,0 +1,153 @@ +from twisted.internet import protocol +from twisted.conch import telnet + +class Interact(telnet.Telnet): + + def connectionMade(self): + print 'Connected' + self.interacting = None + self.cmdbuf = '' + self.honeypotFactory = self.factory.honeypotFactory + + # someone tell me if i'm doing this wrong? + d = self.do(telnet.LINEMODE) + self.requestNegotiation(telnet.LINEMODE, telnet.LINEMODE_EDIT + '\0') + self.will(telnet.ECHO) + + self.transport.write('*** kippo session management console ***\r\n') + self.cmd_help() + + def connectionLost(self, reason): + print 'Connection lost' + if self.interacting != None: + self.interacting.delInteractor(self) + + def enableRemote(self, option): + print 'enableRemote', repr(option) + return option == telnet.LINEMODE + + def disableRemote(self, option): + print 'disableRemote', repr(option) + + def applicationDataReceived(self, bytes): + # in command mode, we want to echo characters and buffer the input + if not self.interacting: + self.transport.write(bytes) + if bytes == '\r': + self.transport.write('\n') + pieces = self.cmdbuf.split(' ', 1) + self.cmdbuf = '' + cmd, args = pieces[0], '' + if len(pieces) > 1: + args = pieces[1] + try: + func = getattr(self, 'cmd_' + cmd) + except AttributeError: + print 'Unknown command: %s' % (cmd,) + self.transport.write('** Unknown command.\r\n') + return + func(args) + else: + self.cmdbuf += bytes + + # in non-command mode we are passing input to the session we are + # watching + else: + for c in bytes: + if ord(c) == 27: # escape + self.interacting.delInteractor(self) + self.interacting = None + self.transport.write( + '\r\n** Interactive session closed.\r\n') + return + if not self.readonly: + self.interacting.keystrokeReceived(bytes, None) + + def sessionWrite(self, data): + buf, prev = '', '' + for c in data: + if c == '\n' and prev != '\r': + buf += '\r\n' + else: + buf += c + prev = c + self.transport.write(buf) + + def sessionClosed(self): + self.interacting.delInteractor(self) + self.interacting = None + self.transport.write('\r\n** Interactive session disconnected.\r\n') + + def cmd_hijack(self, args): + self.cmd_view(args) + self.readonly = False + + def cmd_view(self, args): + self.readonly = True + try: + sessionno = int(args) + except ValueError: + self.transport.write('** Invalid session ID.\r\n') + return + for s in self.honeypotFactory.sessions: + transport = s.terminal.transport.session.conn.transport + if sessionno == transport.transport.sessionno: + self.view(s) + return + self.transport.write('** No such session found.\r\n') + + def view(self, session): + transport = session.terminal.transport.session.conn.transport + sessionno = transport.transport.sessionno + self.transport.write( + '** Attaching to #%d, hit ESC to return\r\n' % sessionno) + session.addInteractor(self) + self.interacting = session + + def cmd_list(self, args): + self.transport.write('ID clientIP clientVersion\r\n') + for s in self.honeypotFactory.sessions: + transport = s.terminal.transport.session.conn.transport + sessionno = transport.transport.sessionno + self.transport.write('%s %s %s\r\n' % \ + (str(sessionno).ljust(4), + s.realClientIP.ljust(15), + s.clientVersion)) + + def cmd_help(self, args = ''): + self.transport.write('List of commands:\r\n') + self.transport.write(' list - list all active sessions\r\n') + self.transport.write( + ' view - attach to a session in read-only mode\r\n') + self.transport.write( + ' hijack - attach to a session in interactive mode\r\n') + self.transport.write( + ' disconnect - disconnect a session\r\n') + self.transport.write(' help - this help\r\n') + self.transport.write(' exit - disconnect the console\r\n') + + def cmd_disconnect(self, args): + try: + sessionno = int(args) + except ValueError: + self.transport.write('** Invalid session ID.\r\n') + return + for s in self.honeypotFactory.sessions: + transport = s.terminal.transport.session.conn.transport + if sessionno == transport.transport.sessionno: + self.transport.write( + '** Disconnecting session #%d\r\n' % sessionno) + transport.loseConnection() + return + self.transport.write('** No such session found.\r\n') + + def cmd_exit(self, args = ''): + self.transport.loseConnection() + +def makeInteractFactory(honeypotFactory): + ifactory = protocol.Factory() + ifactory.protocol = Interact + ifactory.honeypotFactory = honeypotFactory + return ifactory + +# vim: set sw=4 et: