diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcadb2c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/.gitignore b/.gitignore index 65c18ef..d81dd89 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ __pycache__/ env/ env1/ env2/ + +sftp-config.json diff --git a/debian.sh b/debian.sh new file mode 100644 index 0000000..cdec535 --- /dev/null +++ b/debian.sh @@ -0,0 +1,134 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: kippo +# Required-Start: $remote_fs $syslog $network mysql +# Required-Stop: $remote_fs $syslog $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: SSH honeypot +# Description: SSH honeypot +### END INIT INFO + +# Author: Kevin Valk +# + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/usr/local/bin:$PATH +DESC="Kippo SSH honeypot" +NAME=kippo +DAEMON_DIR=/var/software/$NAME +DAEMON=$DAEMON_DIR/$NAME.tac +DAEMON_ARGS="" +TWISTD=/usr/local/bin/twistd +PIDFILE=$DAEMON_DIR/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME +LOGFILE=log/$NAME.log + +# Exit if the package is not installed +[ -x $TWISTD ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. +. /lib/lsb/init-functions + +VERBOSE=yes + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --chdir $DAEMON_DIR --chuid $NAME:$NAME --exec $TWISTD --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --chdir $DAEMON_DIR --chuid $NAME:$NAME --exec $TWISTD -- \ + --pidfile=$PIDFILE \ + --logfile=$LOGFILE \ + -y $DAEMON \ + $DAEMON_ARGS \ + || return 2 + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --chdir $DAEMON_DIR --chuid $NAME:$NAME --retry=TERM/30/KILL/5 --pidfile $PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --chdir $DAEMON_DIR --chuid $NAME:$NAME --oknodo --retry=0/30/KILL/5 --exec $TWISTD + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$TWISTD" "$NAME" && exit 0 || exit $? + ;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/doc/sql/mysql.sql b/doc/sql/mysql.sql index 2e04ee9..3dba285 100644 --- a/doc/sql/mysql.sql +++ b/doc/sql/mysql.sql @@ -27,7 +27,8 @@ CREATE TABLE `input` ( CREATE TABLE `sensors` ( `id` int(11) NOT NULL auto_increment, - `ip` varchar(15) NOT NULL, + `version` TINYINT NOT NULL, + `ip` varchar(45) NOT NULL, PRIMARY KEY (`id`) ) ; @@ -36,7 +37,8 @@ CREATE TABLE `sessions` ( `starttime` datetime NOT NULL, `endtime` datetime default NULL, `sensor` int(4) NOT NULL, - `ip` varchar(15) NOT NULL default '', + `version` TINYINT NOT NULL, + `ip` varchar(45) NOT NULL default '', `termsize` varchar(7) default NULL, `client` int(4) default NULL, PRIMARY KEY (`id`), diff --git a/doc/sql/update8.sql b/doc/sql/update8.sql new file mode 100644 index 0000000..43cd9cd --- /dev/null +++ b/doc/sql/update8.sql @@ -0,0 +1,4 @@ +ALTER TABLE `sessions` CHANGE `ip` `ip` VARCHAR(45) NOT NULL DEFAULT ''; +ALTER TABLE `sensors` CHANGE `ip` `ip` VARCHAR(45) NOT NULL DEFAULT ''; +ALTER TABLE `sessions` ADD `version` TINYINT NOT NULL AFTER `sensor`; +ALTER TABLE `sensors` ADD `version` TINYINT NOT NULL AFTER `id`; diff --git a/kippo/core/dblog.py b/kippo/core/dblog.py index 6983d04..54c1b67 100644 --- a/kippo/core/dblog.py +++ b/kippo/core/dblog.py @@ -8,11 +8,14 @@ def __init__(self, cfg): self.cfg = cfg self.sessions = {} self.ttylogs = {} - self.re_connected = re.compile( - '^New connection: ([0-9.]+):([0-9]+) \(([0-9.]+):([0-9]+)\) ' + \ + self.re_connected_ipv4 = re.compile( + '^New connection: (?:::ffff:)?([0-9.]+):([0-9]+) \((?:::ffff:)?([0-9.]+):([0-9]+)\) ' + \ + '\[session: ([0-9]+)\]$') + self.re_connected_ipv6 = re.compile( + '^New connection: ([0-9a-f:]+):([0-9]+) \(([0-9a-f:]+):([0-9]+)\) ' + \ '\[session: ([0-9]+)\]$') self.re_sessionlog = re.compile( - '.*HoneyPotTransport,([0-9]+),[0-9.]+$') + '.*HoneyPotTransport,([0-9]+),(?:(?:::ffff:)?[0-9.]+|[0-9a-f:]+)$') # :dispatch: means the message has been delivered directly via # logDispatch, instead of relying on the twisted logging, which breaks @@ -65,13 +68,23 @@ def nowUnix(self): def emit(self, ev): if not len(ev['message']): return - match = self.re_connected.match(ev['message'][0]) + + # Test for IPv4 + match = self.re_connected_ipv4.match(ev['message'][0]) + ipv = 4 + + # Test for IPv6 + if not match: + match = self.re_connected_ipv6.match(ev['message'][0]) + ipv = 6 + + # If IPv4 or IPv6 then we golden! if match: sessionid = int(match.groups()[4]) self.sessions[sessionid] = \ self.createSession( match.groups()[0], int(match.groups()[1]), - match.groups()[2], int(match.groups()[3])) + match.groups()[2], int(match.groups()[3]), ipv) return match = self.re_sessionlog.match(ev['system']) if not match: @@ -102,7 +115,7 @@ def ttylog(self, session): return ttylog # We have to return an unique ID - def createSession(self, peerIP, peerPort, hostIP, hostPort): + def createSession(self, peerIP, peerPort, hostIP, hostPort, versionIP): return 0 # args has: logfile diff --git a/kippo/dblog/mysql.py b/kippo/dblog/mysql.py index 1245387..56209e6 100644 --- a/kippo/dblog/mysql.py +++ b/kippo/dblog/mysql.py @@ -53,14 +53,14 @@ def simpleQuery(self, sql, args): d = self.db.runQuery(sql, args) d.addErrback(self.sqlerror) - def createSession(self, peerIP, peerPort, hostIP, hostPort): + def createSession(self, peerIP, peerPort, hostIP, hostPort, versionIP): sid = uuid.uuid1().hex - self.createSessionWhenever(sid, peerIP, hostIP) + self.createSessionWhenever(sid, peerIP, hostIP, versionIP) return sid # This is separate since we can't return with a value @defer.inlineCallbacks - def createSessionWhenever(self, sid, peerIP, hostIP): + def createSessionWhenever(self, sid, peerIP, hostIP, versionIP): sensorname = self.getSensor() or hostIP r = yield self.db.runQuery( 'SELECT `id` FROM `sensors` WHERE `ip` = %s', (sensorname,)) @@ -68,14 +68,14 @@ def createSessionWhenever(self, sid, peerIP, hostIP): id = r[0][0] else: yield self.db.runQuery( - 'INSERT INTO `sensors` (`ip`) VALUES (%s)', (sensorname,)) + 'INSERT INTO `sensors` (`version`, `ip`) VALUES (%s, %s)', (versionIP, sensorname,)) r = yield self.db.runQuery('SELECT LAST_INSERT_ID()') id = int(r[0][0]) # now that we have a sensorID, continue creating the session self.simpleQuery( - 'INSERT INTO `sessions` (`id`, `starttime`, `sensor`, `ip`)' + \ - ' VALUES (%s, FROM_UNIXTIME(%s), %s, %s)', - (sid, self.nowUnix(), id, peerIP)) + 'INSERT INTO `sessions` (`id`, `starttime`, `sensor`, `version`, `ip`)' + \ + ' VALUES (%s, FROM_UNIXTIME(%s), %s, %s, %s)', + (sid, self.nowUnix(), id, versionIP, peerIP)) def handleConnectionLost(self, session, args): ttylog = self.ttylog(session) diff --git a/kippo/dblog/textlog.py b/kippo/dblog/textlog.py index 383cef7..d2713e3 100644 --- a/kippo/dblog/textlog.py +++ b/kippo/dblog/textlog.py @@ -19,7 +19,7 @@ def write(self, session, msg): (session, time.strftime('%Y-%m-%d %H:%M:%S'), msg)) self.outfile.flush() - def createSession(self, peerIP, peerPort, hostIP, hostPort): + def createSession(self, peerIP, peerPort, hostIP, hostPort, versionIP): sid = uuid.uuid1().hex sensorname = self.getSensor() or hostIP self.write(sid, 'New connection: %s:%s' % (peerIP, peerPort)) diff --git a/kippo/dblog/xmpp.py b/kippo/dblog/xmpp.py index b6413a1..1ebd6a9 100644 --- a/kippo/dblog/xmpp.py +++ b/kippo/dblog/xmpp.py @@ -102,10 +102,11 @@ def report(self, msgtype, to, xmsg): self.muc.groupChat(to, None, children=[body]) # We have to return an unique ID - def createSession(self, peerIP, peerPort, hostIP, hostPort): + def createSession(self, peerIP, peerPort, hostIP, hostPort, versionIP): session = uuid.uuid4().hex ses = domish.Element((None, 'session')) ses['session'] = session + ses['remote_ipv'] = versionIP ses['remote_host'] = peerIP ses['remote_port'] = str(peerPort) if self.anonymous == True: