Merge branch 'master' into output-plugin

Conflicts:
	kippo/core/ssh.py
This commit is contained in:
Michel Oosterhof 2015-02-25 17:59:41 +00:00
commit d4d3bdbe0e
18 changed files with 304 additions and 231 deletions

View File

@ -1,3 +1,8 @@
* 2015-02-25 Internals for dblog/ modules changed completely. Now accepts structured logging arguments, and uses eventids instead of regex parsing
* 2015-02-20 Removed screen clear/reset on logout
* 2015-02-19 Configuration directives have changed! ssh_addr has become listen_addr and ssh_port has become listen_port. The old keywords are still accepted for backwards compatibility
* default behaviour is changed to disable the exit jail
* sftp support
* exec support
@ -6,7 +11,7 @@
* allow wget download over non-80 port
* simple JSON logging to kippo.json
* accept log and deny publickey authentication
* add uname -r command
* add uname -r, -m flags
* add working sleep command
* enabled ssh diffie-hellman-group-exchange-sha1 algorithm
* add 'bash -c' support (no effect option)
@ -33,3 +38,4 @@
* add 'poweroff' 'halt' and 'reboot' aliases for shutdown
* add environment passing to commands
* added 'which', 'netstat' and 'gcc' from kippo-extra
* logging framework allows for keyword use

View File

@ -16,12 +16,12 @@
# IP addresses to listen for incoming SSH connections.
#
# (default: 0.0.0.0) = any address
#ssh_addr = 0.0.0.0
#listen_addr = 0.0.0.0
# Port to listen for incoming SSH connections.
#
# (default: 2222)
ssh_port = 2222
#listen_port = 2222
# Hostname for the honeypot. Displayed by the shell prompt of the virtual
# environment.
@ -216,7 +216,7 @@ interact_port = 5123
#logfile = log/kippo-textlog.log
# JSON based logging module
#[database_jsonlog]
[database_jsonlog]
logfile = log/kippolog.json
#[database_hpfeeds]

View File

@ -3,13 +3,11 @@
import sys, os
if sys.platform == 'win32':
import os, inspect
# this is when just running on win32
sys.path.insert(0, os.path.abspath(os.getcwd()))
# and this is when running as a service
#os.chdir(os.path.dirname(inspect.getfile(inspect.currentframe())))
from twisted.internet import reactor, defer
from twisted.application import internet, service
from twisted.cred import portal
from twisted.conch.ssh import factory, keys
@ -23,7 +21,6 @@ if not os.path.exists('kippo.cfg'):
sys.exit(1)
from kippo.core.config import config
import kippo.core.auth
import kippo.core.honeypot
import kippo.core.ssh
from kippo import core
@ -42,16 +39,27 @@ factory.privateKeys = {'ssh-rsa': keys.Key.fromString(data=rsa_privKeyString),
'ssh-dss': keys.Key.fromString(data=dsa_privKeyString)}
cfg = config()
if cfg.has_option('honeypot', 'ssh_addr'):
ssh_addr = cfg.get('honeypot', 'ssh_addr')
if cfg.has_option('honeypot', 'listen_addr'):
listen_addr = cfg.get('honeypot', 'listen_addr')
elif cfg.has_option('honeypot', 'ssh_addr'):
# ssh_addr for backwards compatibility
listen_addr = cfg.get('honeypot', 'ssh_addr')
else:
ssh_addr = '0.0.0.0'
listen_addr = '0.0.0.0'
if cfg.has_option('honeypot', 'listen_port'):
listen_port = int(cfg.get('honeypot', 'listen_port'))
elif cfg.has_option('honeypot', 'ssh_port'):
# ssh_port for backwards compatibility
listen_port = int(cfg.get('honeypot', 'ssh_port'))
else:
listen_port = 2222
application = service.Application('honeypot')
for i in ssh_addr.split():
service = internet.TCPServer(
int(cfg.get('honeypot', 'ssh_port')), factory,
interface=i)
for i in listen_addr.split():
service = internet.TCPServer( listen_port,
factory, interface=i)
service.setServiceParent(application)
if cfg.has_option('honeypot', 'interact_enabled') and \
@ -59,7 +67,6 @@ if cfg.has_option('honeypot', 'interact_enabled') and \
('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)

View File

@ -230,7 +230,9 @@ class command_passwd(HoneyPotCommand):
self.exit()
def lineReceived(self, line):
log.msg( 'INPUT (passwd):', line )
#log.msg( 'INPUT (passwd):', line )
log.msg( eventid='KIPP0008', realm='passwd', input=line,
format='INPUT (%(realm)s): %(input)s' )
self.password = line.strip()
self.callbacks.pop(0)(line)
commands['/usr/bin/passwd'] = command_passwd
@ -428,7 +430,9 @@ class command_perl(HoneyPotCommand):
self.exit()
def lineReceived(self, line):
log.msg( 'INPUT (perl):', line )
#log.msg( 'INPUT (perl):', line )
log.msg( eventid='KIPP0008', realm='perl', input=line,
format='INPUT (%(realm)s): %(input)s' )
commands['/usr/bin/perl'] = command_perl
@ -492,7 +496,9 @@ class command_php(HoneyPotCommand):
self.exit()
def lineReceived(self, line):
log.msg( 'INPUT (php):', line )
#log.msg( 'INPUT (php):', line )
log.msg( eventid='KIPP0008', realm='php', input=line,
format='INPUT (%(realm)s): %(input)s' )
commands['/usr/bin/php'] = command_php

View File

@ -12,6 +12,8 @@ class command_uname(HoneyPotCommand):
self.honeypot.hostname)
elif len(self.args) and self.args[0].strip() in ('-r', '--kernel-release'):
self.writeln( '2.6.26-2-686' )
elif len(self.args) and self.args[0].strip() in ('-m', '--machine'):
self.writeln( 'i686' )
else:
self.writeln('Linux')

View File

@ -143,29 +143,34 @@ class command_wget(HoneyPotCommand):
def success(self, data, outfile):
if not os.path.isfile(self.safeoutfile):
print "there's no file " + self.safeoutfile
log.msg("there's no file " + self.safeoutfile)
self.exit()
shasum = hashlib.sha256(open(self.safeoutfile, 'rb').read()).hexdigest()
hash_path = '%s/%s' % (self.download_path, shasum)
msg = 'SHA sum %s of URL %s in file %s' % \
(shasum, self.url, self.fileName)
print msg
self.honeypot.logDispatch(msg)
# if we have content already, delete temp file
if not os.path.exists(hash_path):
print "moving " + self.safeoutfile + " -> " + hash_path
os.rename(self.safeoutfile, hash_path)
else:
print "deleting " + self.safeoutfile + " SHA sum: " + shasum
os.remove(self.safeoutfile)
log.msg("Not storing duplicate content " + shasum)
self.honeypot.logDispatch( format='Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s',
eventid='KIPP0007', url=self.url, outfile=hash_path, shasum=shasum )
log.msg( format='Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s',
eventid='KIPP0007', url=self.url, outfile=hash_path, shasum=shasum )
# link friendly name to hash
os.symlink( shasum, self.safeoutfile )
# FIXME: is this necessary?
self.safeoutfile = hash_path
print "Updating realfile to " + hash_path
# update the honeyfs to point to downloaded file
f = self.fs.getfile(outfile)
f[9] = hash_path
f[A_REALFILE] = hash_path
self.exit()
def error(self, error, url):
@ -224,10 +229,6 @@ class HTTPProgressDownloader(client.HTTPDownloader):
(self.wget.url,) )
self.fileName = os.path.devnull
self.nomore = True
else:
msg = 'Saving URL (%s) to %s' % (self.wget.url, self.fileName)
self.wget.honeypot.logDispatch(msg)
log.msg( msg )
self.wget.writeln('Saving to: `%s' % self.fakeoutfile)
self.wget.honeypot.terminal.nextLine()

View File

@ -16,7 +16,7 @@ from twisted.python import log, failure
from twisted.conch import error
from twisted.conch.ssh import keys
from kippo.core.config import config
from config import config
# by Walter de Jong <walter@sara.nl>
class UserDB(object):
@ -156,12 +156,18 @@ class HoneypotPasswordChecker:
return defer.succeed(username)
return defer.fail(UnauthorizedLogin())
def checkUserPass(self, username, password):
if UserDB().checklogin(username, password):
log.msg( 'login attempt [%s/%s] succeeded' % (username, password) )
def checkUserPass(self, theusername, thepassword):
if UserDB().checklogin(theusername, thepassword):
#log.msg( 'login attempt [%s/%s] succeeded' % (theusername, thepassword) )
log.msg( eventid='KIPP0002',
format='login attempt [%(username)s/%(password)s] succeeded',
username=theusername, password=thepassword )
return True
else:
log.msg( 'login attempt [%s/%s] failed' % (username, password) )
#log.msg( 'login attempt [%s/%s] failed' % (theusername, thepassword) )
log.msg( eventid='KIPP0003',
format='login attempt [%(username)s/%(password)s] failed',
username=theusername, password=thepassword )
return False
# vim: set sw=4 et:

View File

@ -3,59 +3,60 @@
import re
import time
import abc
# dblog now operates based on eventids, no longer on regex parsing of the entry.
# add an eventid using keyword args and it will be picked up by the dblogger
# the KIPPxxxx naming convention is still subject to change.
# KIPP0001 : create session
# KIPP0002 : succesful login
# KIPP0003 : failed login
# KIPP0004 : TTY log opened
# KIPP0005 : handle command
# KIPP0006 : handle unknown command
# KIPP0007 : file download
# KIPP0008 : INPUT
# KIPP0009 : SSH Version
# KIPP0010 : Terminal Size
# KIPP0011 : Connection Lost
# KIPP0012 : TTY log closed
class DBLogger(object):
__metaclass__ = abc.ABCMeta
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]+)\) ' + \
'\[session: ([0-9]+)\]$')
self.re_sessionlog = re.compile(
'.*HoneyPotTransport,([0-9]+),[0-9.]+$')
# :dispatch: means the message has been delivered directly via
# logDispatch, instead of relying on the twisted logging, which breaks
# on scope changes.
self.re_map = [(re.compile(x[0]), x[1]) for x in (
('^connection lost$',
self._connectionLost),
('^login attempt \[(?P<username>.*)/(?P<password>.*)\] failed',
self.handleLoginFailed),
('^login attempt \[(?P<username>.*)/(?P<password>.*)\] succeeded',
self.handleLoginSucceeded),
('^Opening TTY log: (?P<logfile>.*)$',
self.handleTTYLogOpened),
('^:dispatch: Command found: (?P<input>.*)$',
self.handleCommand),
('^:dispatch: Command not found: (?P<input>.*)$',
self.handleUnknownCommand),
('^:dispatch: Saving URL \((?P<url>.*)\) to (?P<outfile>.*)$',
self.handleFileDownload),
('^:dispatch: SHA sum (?P<shasum>.*) of URL (?P<url>.*) in file (?P<outfile>.*)$',
self.handleShaSum),
('^:dispatch: Updated outfile (?P<outfile>.*) to (?P<dl_file>.*) with SHA sum (?P<shasum>.*)$',
self.handleUpdatedFile),
('^INPUT \((?P<realm>[a-zA-Z0-9]+)\): (?P<input>.*)$',
self.handleInput),
('^Terminal size: (?P<height>[0-9]+) (?P<width>[0-9]+)$',
self.handleTerminalSize),
('^Remote SSH version: (?P<version>.*)$',
self.handleClientVersion),
)]
# KIPP0001 is special since it kicks off new logging session,
# and is not handled here
self.events = {
'KIPP0002': self.handleLoginSucceeded,
'KIPP0003': self.handleLoginFailed,
'KIPP0004': self.handleTTYLogOpened,
'KIPP0005': self.handleCommand,
'KIPP0006': self.handleUnknownCommand,
'KIPP0007': self.handleFileDownload,
'KIPP0008': self.handleInput,
'KIPP0009': self.handleClientVersion,
'KIPP0010': self.handleTerminalSize,
'KIPP0011': self._connectionLost,
}
self.start(cfg)
def logDispatch(self, sessionid, msg):
if sessionid not in self.sessions.keys():
return
for regex, func in self.re_map:
match = regex.match(msg)
if match:
func(self.sessions[sessionid], match.groupdict())
break
# used when the HoneypotTransport prefix is not available.
def logDispatch(self, *msg, **kw):
ev = kw
ev['message'] = msg
self.emit(ev)
def start():
def start(self, cfg):
"""Hook that can be used to set up connections in dbloggers"""
pass
def getSensor(self):
@ -68,28 +69,42 @@ class DBLogger(object):
return int(time.mktime(time.gmtime()[:-1] + (-1,)))
def emit(self, ev):
if not len(ev['message']):
# ignore stdout and stderr
if 'printed' in ev:
return
match = self.re_connected.match(ev['message'][0])
if match:
sessionid = int(match.groups()[4])
self.sessions[sessionid] = \
# ignore anything without eventid
if not 'eventid' in ev:
return
# connection event is special. adds to list
if ev['eventid'] == 'KIPP0001':
sessionno = ev['sessionno']
self.sessions[sessionno] = \
self.createSession(
match.groups()[0], int(match.groups()[1]),
match.groups()[2], int(match.groups()[3]))
ev['src_ip'], ev['src_port'], ev['dst_ip'], ev['dst_port'] )
return
match = self.re_sessionlog.match(ev['system'])
if not match:
# use explicit sessionno if coming from dispatch
if 'sessionno' in ev:
sessionno = ev['sessionno']
del ev['sessionno']
# else extract session id from the twisted log prefix
elif 'system' in ev:
match = self.re_sessionlog.match(ev['system'])
if not match:
return
sessionno = int(match.groups()[0])
if sessionno not in self.sessions.keys():
return
sessionid = int(match.groups()[0])
if sessionid not in self.sessions.keys():
return
message = ev['message'][0]
for regex, func in self.re_map:
match = regex.match(message)
if match:
func(self.sessions[sessionid], match.groupdict())
break
if 'eventid' in ev:
if ev['eventid'] in self.events:
self.events[ev['eventid']]( self.sessions[sessionno], ev )
return
print "error, unknown eventid %s" % repr(ev)
def _connectionLost(self, session, args):
self.handleConnectionLost(session, args)
@ -106,7 +121,8 @@ class DBLogger(object):
f.close()
return ttylog
# We have to return an unique ID
# We have to return a unique ID
@abc.abstractmethod
def createSession(self, peerIP, peerPort, hostIP, hostPort):
return 0
@ -150,12 +166,4 @@ class DBLogger(object):
def handleFileDownload(self, session, args):
pass
# args has: shasum, url, outfile
def handleShaSum(self, session, args):
pass
# args has: outfile, dl_file, shasum
def handleUpdatedFile(self, session, args):
pass
# vim: set sw=4 et:

View File

@ -8,7 +8,7 @@ import re
import stat
import errno
from kippo.core.config import config
from config import config
A_NAME, \
A_TYPE, \

View File

@ -9,9 +9,10 @@ import copy
import pickle
from twisted.python import log
from kippo.core import fs
from kippo.core.config import config
import kippo.core.exceptions
import fs
import exceptions
from config import config
class HoneyPotCommand(object):
def __init__(self, protocol, *args):
@ -124,12 +125,15 @@ class HoneyPotShell(object):
rargs.append(arg)
cmdclass = self.honeypot.getCommand(cmd, envvars['PATH'].split(':'))
if cmdclass:
log.msg( 'Command found: %s' % (line,) )
self.honeypot.logDispatch('Command found: %s' % (line,))
#log.msg( 'Command found: %s' % (line,) )
log.msg( eventid='KIPP0005', input=line, format='Command found: %(input)s' )
#self.honeypot.logDispatch('Command found: %s' % (line,))
self.honeypot.call_command(cmdclass, *rargs)
else:
self.honeypot.logDispatch('Command not found: %s' % (line,))
log.msg( 'Command not found: %s' % (line,) )
#log.msg( 'Command not found: %s' % (line,) )
log.msg( eventid='KIPP0006',
input=line, format='Command not found: %(input)s' )
#self.honeypot.logDispatch('Command not found: %s' % (line,))
if len(line):
self.honeypot.writeln('bash: %s: command not found' % cmd)
runOrPrompt()

View File

@ -6,7 +6,7 @@ import time
from twisted.internet import protocol
from twisted.conch import telnet, recvline
from kippo.core import ttylog
import ttylog
class Interact(telnet.Telnet):

View File

@ -3,20 +3,16 @@
import os
import time
import struct
import socket
import copy
from twisted.conch import recvline
from twisted.conch.ssh import transport
from twisted.conch.insults import insults
from twisted.internet import protocol
from twisted.python import log
from kippo.core import ttylog, fs
from kippo.core.config import config
import kippo.core.honeypot
from kippo import core
import honeypot
import ttylog
from config import config
class HoneyPotBaseProtocol(insults.TerminalProtocol):
def __init__(self, avatar, env):
@ -33,10 +29,10 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol):
self.password_input = False
self.cmdstack = []
def logDispatch(self, msg):
def logDispatch(self, *msg, **args):
transport = self.terminal.transport.session.conn.transport
msg = ':dispatch: ' + msg
transport.factory.logDispatch(transport.transport.sessionno, msg)
args['sessionno']=transport.transport.sessionno
transport.factory.logDispatch(*msg,**args)
def connectionMade(self):
self.displayMOTD()
@ -47,7 +43,6 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol):
self.realClientPort = transport.transport.getPeer().port
self.clientVersion = transport.otherVersionString
self.logintime = transport.logintime
self.ttylog_file = transport.ttylog_file
# source IP of client in user visible reports (can be fake or real)
cfg = config()
@ -61,7 +56,7 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol):
else:
# Hack to get ip
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8",80))
s.connect(("8.8.8.8", 80))
self.kippoIP = s.getsockname()[0]
s.close()
@ -71,17 +66,16 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol):
except:
pass
# this doesn't seem to be called upon disconnect, so please use
# HoneyPotTransport.connectionLost instead
# this is only called on explicit logout, not on disconnect
def connectionLost(self, reason):
pass
log.msg( eventid='KIPP0011', format='Connection lost')
# not sure why i need to do this:
# scratch that, these don't seem to be necessary anymore:
#del self.fs
#del self.commands
def txtcmd(self, txt):
class command_txtcmd(core.honeypot.HoneyPotCommand):
class command_txtcmd(honeypot.HoneyPotCommand):
def call(self):
log.msg( 'Reading txtcmd from "%s"' % txt )
f = file(txt, 'r')
@ -149,9 +143,9 @@ class HoneyPotExecProtocol(HoneyPotBaseProtocol):
def connectionMade(self):
HoneyPotBaseProtocol.connectionMade(self)
self.terminal.transport.session.conn.transport.stdinlog_open = True
self.terminal.stdinlog_open = True
self.cmdstack = [core.honeypot.HoneyPotShell(self, interactive=False)]
self.cmdstack = [honeypot.HoneyPotShell(self, interactive=False)]
self.cmdstack[0].lineReceived(self.execcmd)
class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLine):
@ -164,7 +158,7 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin
HoneyPotBaseProtocol.connectionMade(self)
recvline.HistoricRecvLine.connectionMade(self)
self.cmdstack = [core.honeypot.HoneyPotShell(self)]
self.cmdstack = [honeypot.HoneyPotShell(self)]
transport = self.terminal.transport.session.conn.transport
transport.factory.sessions[transport.transport.sessionno] = self
@ -234,22 +228,27 @@ 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 connectionMade(self):
transport = self.transport.session.conn.transport
transport.ttylog_file = '%s/tty/%s-%s.log' % \
(config().get('honeypot', 'log_path'),
time.strftime('%Y%m%d-%H%M%S'), transport.transportId )
log.msg( 'Opening TTY log: %s' % transport.ttylog_file )
ttylog.ttylog_open(transport.ttylog_file, time.time())
transport.ttylog_open = True
transport.stdinlog_file = '%s/%s-%s-stdin.log' % \
self.ttylog_file = transport.ttylog_file
log.msg( eventid='KIPP0004', logfile=transport.ttylog_file,
format='Opening TTY Log: %(logfile)s')
ttylog.ttylog_open(transport.ttylog_file, time.time())
self.ttylog_open = True
self.stdinlog_file = '%s/%s-%s-stdin.log' % \
(config().get('honeypot', 'download_path'),
time.strftime('%Y%m%d-%H%M%S'), transport.transportId )
transport.stdinlog_open = False
self.stdinlog_open = False
insults.ServerProtocol.connectionMade(self)
@ -257,25 +256,39 @@ class LoggingServerProtocol(insults.ServerProtocol):
transport = self.transport.session.conn.transport
for i in transport.interactors:
i.sessionWrite(bytes)
if transport.ttylog_open and not noLog:
if self.ttylog_open and not noLog:
ttylog.ttylog_write(transport.ttylog_file, len(bytes),
ttylog.TYPE_OUTPUT, time.time(), bytes)
insults.ServerProtocol.write(self, bytes)
def dataReceived(self, data, noLog = False):
transport = self.transport.session.conn.transport
if transport.ttylog_open and not noLog:
if self.ttylog_open and not noLog:
ttylog.ttylog_write(transport.ttylog_file, len(data),
ttylog.TYPE_INPUT, time.time(), data)
if transport.stdinlog_open and not noLog:
f = file( transport.stdinlog_file, 'ab' )
if self.stdinlog_open and not noLog:
log.msg( "Saving stdin log: %s" % self.stdinlog_file )
f = file( self.stdinlog_file, 'ab' )
f.write(data)
f.close
insults.ServerProtocol.dataReceived(self, data)
# this doesn't seem to be called upon disconnect, so please use
# HoneyPotTransport.connectionLost instead
# override super to remove the terminal reset on logout
def loseConnection(self):
self.transport.loseConnection()
# FIXME: this method is called 4 times on logout....
# it's called once from Avatar.closed() if disconnected
def connectionLost(self, reason):
# log.msg( "received call to LSP.connectionLost" )
transport = self.transport.session.conn.transport
if self.ttylog_open:
log.msg( eventid='KIPP0012', format='Closing TTY Log: %(ttylog)s',
ttylog=transport.ttylog_file)
ttylog.ttylog_close(transport.ttylog_file, time.time())
self.ttylog_open = False
insults.ServerProtocol.connectionLost(self, reason)
# vim: set sw=4 et:

View File

@ -21,13 +21,16 @@ from twisted.conch.ssh.common import NS, getNS
import ConfigParser
from kippo.core import ttylog, utils, fs, sshserver
from kippo.core.config import config
import kippo.core.auth
import kippo.core.honeypot
import kippo.core.ssh
import kippo.core.protocol
from kippo import core
import utils
import fs
import sshserver
import auth
import honeypot
import ssh
import protocol
import sshserver
import exceptions
from config import config
class HoneyPotSSHUserAuthServer(userauth.SSHUserAuthServer):
def serviceStarted(self):
@ -63,11 +66,11 @@ class HoneyPotSSHFactory(factory.SSHFactory):
}
# Special delivery to the loggers to avoid scope problems
def logDispatch(self, sessionid, msg):
def logDispatch(self, *msg, **args):
for dblog in self.dbloggers:
dblog.logDispatch(sessionid, msg)
dblog.logDispatch(*msg, **args)
for output in self.output_plugins:
output.logDispatch(sessionid, msg)
output.logDispatch(*msg, **args)
def __init__(self):
cfg = config()
@ -168,7 +171,7 @@ class HoneyPotRealm:
def __init__(self):
# I don't know if i'm supposed to keep static stuff here
self.env = core.honeypot.HoneyPotEnvironment()
self.env = honeypot.HoneyPotEnvironment()
def requestAvatar(self, avatarId, mind, *interfaces):
if conchinterfaces.IConchUser in interfaces:
@ -177,43 +180,35 @@ class HoneyPotRealm:
else:
raise Exception, "No supported interfaces found."
class HoneyPotTransport(kippo.core.sshserver.KippoSSHServerTransport):
class HoneyPotTransport(sshserver.KippoSSHServerTransport):
"""
@ivar logintime: time of login
@ivar interactors: interactors
@ivar ttylog_open: whether log is open
@ivar transportId: UUID of this transport
@ivar _hadVersion: used so we only send key exchange after receive version info
"""
_hadVersion = False
ttylog_open = False
interactors = []
transportId = ''
def connectionMade(self):
self.logintime = time.time()
self.transportId = uuid.uuid4().hex[:8]
self.interactors = []
log.msg( 'New connection: %s:%s (%s:%s) [session: %d]' % \
(self.transport.getPeer().host, self.transport.getPeer().port,
self.transport.getHost().host, self.transport.getHost().port,
self.transport.sessionno) )
#log.msg( 'New connection: %s:%s (%s:%s) [session: %d]' % \
# (self.transport.getPeer().host, self.transport.getPeer().port,
# self.transport.getHost().host, self.transport.getHost().port,
# self.transport.sessionno) )
log.msg( eventid='KIPP0001',
format='New connection: %(src_ip)s:%(src_port)s (%(dst_ip)s:%(dst_port)s) [session: %(sessionno)s]',
src_ip=self.transport.getPeer().host, src_port=self.transport.getPeer().port,
dst_ip=self.transport.getHost().host, dst_port=self.transport.getHost().port,
sessionno=self.transport.sessionno )
kippo.core.sshserver.KippoSSHServerTransport.connectionMade(self)
sshserver.KippoSSHServerTransport.connectionMade(self)
def sendKexInit(self):
# Don't send key exchange prematurely
if not self.gotVersion:
return
kippo.core.sshserver.KippoSSHServerTransport.sendKexInit(self)
sshserver.KippoSSHServerTransport.sendKexInit(self)
def dataReceived(self, data):
kippo.core.sshserver.KippoSSHServerTransport.dataReceived(self, data)
sshserver.KippoSSHServerTransport.dataReceived(self, data)
# later versions seem to call sendKexInit again on their own
if twisted.version.major < 11 and \
not self._hadVersion and self.gotVersion:
@ -230,8 +225,8 @@ class HoneyPotTransport(kippo.core.sshserver.KippoSSHServerTransport):
log.msg('KEXINIT: client supported MAC: %s' % macCS )
log.msg('KEXINIT: client supported compression: %s' % compCS )
log.msg('KEXINIT: client supported lang: %s' % langCS )
log.msg( 'Remote SSH version: %s' % self.otherVersionString,)
return kippo.core.sshserver.KippoSSHServerTransport.ssh_KEXINIT(self, packet)
log.msg( eventid='KIPP0009', version=self.otherVersionString, format='Remote SSH version: %(version)s' )
return sshserver.KippoSSHServerTransport.ssh_KEXINIT(self, packet)
def lastlogExit(self):
starttime = time.strftime('%a %b %d %H:%M',
@ -245,15 +240,13 @@ class HoneyPotTransport(kippo.core.sshserver.KippoSSHServerTransport):
# this seems to be the only reliable place of catching lost connection
def connectionLost(self, reason):
log.msg( "Connection Lost in SSH Transport" )
for i in self.interactors:
i.sessionClosed()
if self.transport.sessionno in self.factory.sessions:
del self.factory.sessions[self.transport.sessionno]
self.lastlogExit()
if self.ttylog_open:
ttylog.ttylog_close(self.ttylog_file, time.time())
self.ttylog_open = False
kippo.core.sshserver.KippoSSHServerTransport.connectionLost(self, reason)
sshserver.KippoSSHServerTransport.connectionLost(self, reason)
class HoneyPotSSHSession(session.SSHSession):
@ -277,10 +270,17 @@ class HoneyPotSSHSession(session.SSHSession):
log.msg('request_x11: %s' % repr(data) )
return 0
# this is reliably called on session close/disconnect and calls the avatar
def closed(self):
session.SSHSession.closed(self)
def loseConnection(self):
self.conn.sendRequest(self, 'exit-status', "\x00"*4)
session.SSHSession.loseConnection(self)
def channelClosed(self):
log.msg( "Called channelClosed in SSHSession")
# FIXME: recent twisted conch avatar.py uses IConchuser here
@implementer(conchinterfaces.ISession)
class HoneyPotAvatar(avatar.ConchUser):
@ -291,6 +291,7 @@ class HoneyPotAvatar(avatar.ConchUser):
self.env = env
self.fs = fs.HoneyPotFilesystem(copy.deepcopy(self.env.fs))
self.hostname = self.env.cfg.get('honeypot', 'hostname')
self.protocol = None
self.channelLookup.update({'session': HoneyPotSSHSession})
self.channelLookup['direct-tcpip'] = KippoOpenConnectForwardingClient
@ -300,41 +301,52 @@ class HoneyPotAvatar(avatar.ConchUser):
if ( self.env.cfg.get('honeypot', 'sftp_enabled') == "true" ):
self.subsystemLookup['sftp'] = filetransfer.FileTransferServer
self.uid = self.gid = core.auth.UserDB().getUID(self.username)
self.uid = self.gid = auth.UserDB().getUID(self.username)
if not self.uid:
self.home = '/root'
else:
self.home = '/home/' + username
def openShell(self, protocol):
serverProtocol = core.protocol.LoggingServerProtocol(
core.protocol.HoneyPotInteractiveProtocol, self, self.env)
serverProtocol.makeConnection(protocol)
protocol.makeConnection(session.wrapProtocol(serverProtocol))
def openShell(self, proto):
serverProtocol = protocol.LoggingServerProtocol(
protocol.HoneyPotInteractiveProtocol, self, self.env)
self.protocol = serverProtocol
serverProtocol.makeConnection(proto)
proto.makeConnection(session.wrapProtocol(serverProtocol))
#self.protocol = serverProtocol
self.protocol = proto
def getPty(self, terminal, windowSize, attrs):
log.msg( 'Terminal size: %s %s' % windowSize[0:2] )
#log.msg( 'Terminal size: %s %s' % windowSize[0:2] )
log.msg( eventid='KIPP0010', width=windowSize[0], height=windowSize[1],
format='Terminal Size: %(width)s %(height)s' )
self.windowSize = windowSize
return None
def execCommand(self, protocol, cmd):
def execCommand(self, proto, cmd):
cfg = config()
if not cfg.has_option('honeypot', 'exec_enabled') or \
cfg.get('honeypot', 'exec_enabled').lower() not in \
('yes', 'true', 'on'):
log.msg( 'Exec disabled. Not executing command: "%s"' % cmd )
raise core.exceptions.NotEnabledException, \
raise exceptions.NotEnabledException, \
'exec_enabled not enabled in configuration file!'
return
log.msg( 'exec command: "%s"' % cmd )
serverProtocol = kippo.core.protocol.LoggingServerProtocol(
kippo.core.protocol.HoneyPotExecProtocol, self, self.env, cmd)
serverProtocol.makeConnection(protocol)
protocol.makeConnection(session.wrapProtocol(serverProtocol))
serverProtocol = protocol.LoggingServerProtocol(
protocol.HoneyPotExecProtocol, self, self.env, cmd)
self.protocol = serverProtocol
serverProtocol.makeConnection(proto)
proto.makeConnection(session.wrapProtocol(serverProtocol))
self.protocol = serverProtocol
# this is reliably called on both logout and disconnect
# we notify the protocol here we lost the connection
def closed(self):
pass
if self.protocol:
self.protocol.connectionLost("disconnected")
def eofReceived(self):
pass

View File

@ -1,7 +1,7 @@
# Copyright (c) 2010-2014 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
from kippo.core.config import config
from config import config
def addToLastlog(message):
f = file('%s/lastlog.txt' % config().get('honeypot', 'data_path'), 'a')

View File

@ -1,19 +1,44 @@
# Copyright (c) 2015 Michel Oosterhof <michel@oosterhof.net>
# All rights reserved.
#
# this module uses the dblog feature to create a JSON logfile
# ..so not exactly a dblog.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The names of the author(s) may not be used to endorse or promote
# products derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
import datetime
import uuid
import json
from kippo.core import dblog
from twisted.enterprise import adbapi
from twisted.internet import defer
from twisted.python import log
from ..core import dblog
class DBLogger(dblog.DBLogger):
def __init__(self, cfg):
self.sensor = ""
self.outfile = ""
dblog.DBLogger.__init__(self, cfg)
def start(self, cfg):
self.outfile = file(cfg.get('database_jsonlog', 'logfile'), 'a')
@ -38,6 +63,9 @@ class DBLogger(dblog.DBLogger):
def handleConnectionLost(self, session, args):
logentry = { 'message': 'Connection lost' }
self.write( session, logentry )
#ttylog = self.ttylog(session)
#if ttylog:
# self.write( session, { 'message': repr(ttylog) } )
def handleLoginFailed(self, session, args):
logentry = { 'message' : 'Login failed [%s/%s]' % (args['username'], args['password']), 'username' : args['username'], 'password' : args['password'] }
@ -68,12 +96,7 @@ class DBLogger(dblog.DBLogger):
self.write( session, logentry )
def handleFileDownload(self, session, args):
logentry = { 'message' : 'File download: [%s] -> %s' % (args['url'], args['outfile']), 'url' : args['url'] }
self.write( session, logentry )
def handleShaSum(self, session, args):
logentry = { 'message' : 'File SHA sum: %s [%s] -> %s' % \
(args['shasum'], args['url'], args['outfile']), 'shasum' : args['shasum'], 'url' : args['url'] }
logentry = { 'message' : 'File download: [%s] -> %s' % (args['url'], args['outfile']), 'url' : args['url'], 'shasum' : args['shasum'] }
self.write( session, logentry )
# vim: set sw=4 et:

View File

@ -142,13 +142,8 @@ class DBLogger(dblog.DBLogger):
def handleFileDownload(self, session, args):
self.simpleQuery('INSERT INTO `downloads`' + \
' (`session`, `timestamp`, `url`, `outfile`)' + \
' (`session`, `timestamp`, `url`, `outfile`, `shasum`)' + \
' VALUES (%s, FROM_UNIXTIME(%s), %s, %s)',
(session, self.nowUnix(), args['url'], args['outfile']))
def handleShaSum(self, session, args):
self.simpleQuery('UPDATE `downloads` SET `shasum` = %s' + \
' WHERE `outfile` = %s',
(args['shasum'], args['outfile']))
(session, self.nowUnix(), args['url'], args['outfile'], args['shasum']))
# vim: set sw=4 et:

View File

@ -50,15 +50,7 @@ class DBLogger(dblog.DBLogger):
self.write(session, 'Client version: [%s]' % (args['version'],))
def handleFileDownload(self, session, args):
self.write(session, 'File download: [%s] -> %s' % \
(args['url'], args['outfile']))
def handleShaSum(self, session, args):
self.write(session, 'File SHA sum: %s [%s] -> %s' % \
(args['shasum'], args['url'], args['outfile']))
def handleUpdatedFile(self, session, args):
self.write(session, 'Updated wget outfile %s to %s' % \
(args['outfile'], args['dl_file']))
self.write(session, 'File download: [%s] -> %s with SHA-256 %s' % \
(args['url'], args['outfile'], args['shasum']))
# vim: set sw=4 et:

View File

@ -24,7 +24,5 @@ then
. $VENV/bin/activate
fi
twistd --version
echo "Starting kippo in the background..."
twistd -y kippo.tac -l log/kippo.log --pidfile kippo.pid