mirror of https://github.com/desaster/kippo.git
restructuring and cleanup
This commit is contained in:
parent
72a6118849
commit
c3c09adb02
11
.gitignore
11
.gitignore
|
@ -1,11 +0,0 @@
|
|||
*.pyc
|
||||
kippo.cfg
|
||||
kippo.pid
|
||||
data/lastlog.txt
|
||||
data/ssh_host_dsa_key
|
||||
data/ssh_host_dsa_key.pub
|
||||
data/ssh_host_rsa_key
|
||||
data/ssh_host_rsa_key.pub
|
||||
dl/*
|
||||
log/kippo.log
|
||||
log/tty/*
|
|
@ -0,0 +1,66 @@
|
|||
kippo.cfg
|
||||
kippo.pid
|
||||
data/lastlog.txt
|
||||
data/ssh_host_dsa_key
|
||||
data/ssh_host_dsa_key.pub
|
||||
data/ssh_host_rsa_key
|
||||
data/ssh_host_rsa_key.pub
|
||||
dl/*
|
||||
log/kippo.log
|
||||
log/tty/*
|
||||
private.key
|
||||
public.key
|
||||
|
||||
# Created by .gitignore support plugin (hsz.mobi)
|
||||
|
||||
### Python template
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
15
kippo.tac
15
kippo.tac
|
@ -22,15 +22,18 @@ if not os.path.exists('kippo.cfg'):
|
|||
print 'ERROR: kippo.cfg is missing!'
|
||||
sys.exit(1)
|
||||
|
||||
from kippo.core import honeypot
|
||||
from kippo.core.config import config
|
||||
import kippo.core.auth
|
||||
import kippo.core.honeypot
|
||||
import kippo.core.ssh
|
||||
from kippo import core
|
||||
|
||||
factory = honeypot.HoneyPotSSHFactory()
|
||||
factory.portal = portal.Portal(honeypot.HoneyPotRealm())
|
||||
factory = core.ssh.HoneyPotSSHFactory()
|
||||
factory.portal = portal.Portal(core.ssh.HoneyPotRealm())
|
||||
|
||||
rsa_pubKeyString, rsa_privKeyString = honeypot.getRSAKeys()
|
||||
dsa_pubKeyString, dsa_privKeyString = honeypot.getDSAKeys()
|
||||
factory.portal.registerChecker(honeypot.HoneypotPasswordChecker())
|
||||
rsa_pubKeyString, rsa_privKeyString = core.ssh.getRSAKeys()
|
||||
dsa_pubKeyString, dsa_privKeyString = core.ssh.getDSAKeys()
|
||||
factory.portal.registerChecker(core.auth.HoneypotPasswordChecker())
|
||||
factory.publicKeys = {'ssh-rsa': keys.Key.fromString(data=rsa_pubKeyString),
|
||||
'ssh-dss': keys.Key.fromString(data=dsa_pubKeyString)}
|
||||
factory.privateKeys = {'ssh-rsa': keys.Key.fromString(data=rsa_privKeyString),
|
||||
|
|
|
@ -5,7 +5,7 @@ import os, time, anydbm, datetime
|
|||
from kippo.core.honeypot import HoneyPotCommand
|
||||
from twisted.internet import reactor
|
||||
from kippo.core.config import config
|
||||
from kippo.core.userdb import UserDB
|
||||
from kippo.core.auth import UserDB
|
||||
from kippo.core import utils
|
||||
|
||||
commands = {}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
#
|
||||
# userdb.py for kippo
|
||||
# by Walter de Jong <walter@sara.nl>
|
||||
#
|
||||
# adopted and further modified by Upi Tamminen <desaster@gmail.com>
|
||||
#
|
||||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
from kippo.core.config import config
|
||||
import os
|
||||
import string
|
||||
|
||||
class UserDB:
|
||||
import twisted
|
||||
from twisted.cred import checkers, credentials, error
|
||||
from twisted.internet import defer
|
||||
from zope.interface import implements
|
||||
|
||||
from kippo.core.config import config
|
||||
|
||||
# by Walter de Jong <walter@sara.nl>
|
||||
class UserDB(object):
|
||||
|
||||
def __init__(self):
|
||||
self.userdb = []
|
||||
self.load()
|
||||
|
@ -96,4 +99,39 @@ class UserDB:
|
|||
self.userdb.append((login, uid, passwd))
|
||||
self.save()
|
||||
|
||||
class HoneypotPasswordChecker:
|
||||
implements(checkers.ICredentialsChecker)
|
||||
|
||||
credentialInterfaces = (credentials.IUsernamePassword,
|
||||
credentials.IPluggableAuthenticationModules)
|
||||
|
||||
def requestAvatarId(self, credentials):
|
||||
if hasattr(credentials, 'password'):
|
||||
if self.checkUserPass(credentials.username, credentials.password):
|
||||
return defer.succeed(credentials.username)
|
||||
else:
|
||||
return defer.fail(error.UnauthorizedLogin())
|
||||
elif hasattr(credentials, 'pamConversion'):
|
||||
return self.checkPamUser(credentials.username,
|
||||
credentials.pamConversion)
|
||||
return defer.fail(error.UnhandledCredentials())
|
||||
|
||||
def checkPamUser(self, username, pamConversion):
|
||||
r = pamConversion((('Password:', 1),))
|
||||
return r.addCallback(self.cbCheckPamUser, username)
|
||||
|
||||
def cbCheckPamUser(self, responses, username):
|
||||
for response, zero in responses:
|
||||
if self.checkUserPass(username, response):
|
||||
return defer.succeed(username)
|
||||
return defer.fail(error.UnauthorizedLogin())
|
||||
|
||||
def checkUserPass(self, username, password):
|
||||
if UserDB().checklogin(username, password):
|
||||
print 'login attempt [%s/%s] succeeded' % (username, password)
|
||||
return True
|
||||
else:
|
||||
print 'login attempt [%s/%s] failed' % (username, password)
|
||||
return False
|
||||
|
||||
# vim: set sw=4 et:
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
import ConfigParser, os
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
import re, time, socket
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
class NotEnabledException(Exception):
|
||||
""" Feature not enabled
|
||||
"""
|
||||
|
||||
# vim: set sw=4 et:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
import os, time, fnmatch
|
||||
|
|
|
@ -1,26 +1,16 @@
|
|||
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
import twisted
|
||||
from twisted.cred import portal, checkers, credentials, error
|
||||
from twisted.conch import avatar, recvline, interfaces as conchinterfaces
|
||||
from twisted.conch.ssh import factory, userauth, connection, keys, session, common, transport
|
||||
from twisted.conch.insults import insults
|
||||
from twisted.application import service, internet
|
||||
from twisted.internet import reactor, protocol, defer
|
||||
from twisted.python import failure, log
|
||||
from zope.interface import implements
|
||||
from copy import deepcopy, copy
|
||||
import sys, os, random, pickle, time, stat, shlex, anydbm, struct
|
||||
import shlex
|
||||
|
||||
from kippo.core import ttylog, fs, utils
|
||||
from kippo.core.userdb import UserDB
|
||||
from kippo.core import fs
|
||||
from kippo.core.config import config
|
||||
from kippo.core import exceptions
|
||||
import kippo.core.exceptions
|
||||
from kippo import core
|
||||
|
||||
import commands
|
||||
|
||||
import ConfigParser
|
||||
import pickle
|
||||
|
||||
class HoneyPotCommand(object):
|
||||
def __init__(self, honeypot, *args):
|
||||
|
@ -253,296 +243,6 @@ class HoneyPotShell(object):
|
|||
self.honeypot.lineBufferIndex = len(self.honeypot.lineBuffer)
|
||||
self.honeypot.terminal.write(newbuf)
|
||||
|
||||
class HoneyPotBaseProtocol(insults.TerminalProtocol):
|
||||
def __init__(self, user, env):
|
||||
self.user = user
|
||||
self.env = env
|
||||
self.hostname = self.env.cfg.get('honeypot', 'hostname')
|
||||
self.fs = fs.HoneyPotFilesystem(deepcopy(self.env.fs))
|
||||
if self.fs.exists(user.home):
|
||||
self.cwd = user.home
|
||||
else:
|
||||
self.cwd = '/'
|
||||
# commands is also a copy so we can add stuff on the fly
|
||||
self.commands = copy(self.env.commands)
|
||||
self.password_input = False
|
||||
self.cmdstack = []
|
||||
|
||||
def logDispatch(self, msg):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
msg = ':dispatch: ' + msg
|
||||
transport.factory.logDispatch(transport.transport.sessionno, msg)
|
||||
|
||||
def connectionMade(self):
|
||||
self.displayMOTD()
|
||||
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
|
||||
#transport = self.transport.transport.session.conn.transport
|
||||
self.realClientIP = transport.getPeer().address.host
|
||||
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()
|
||||
if cfg.has_option('honeypot', 'fake_addr'):
|
||||
self.clientIP = cfg.get('honeypot', 'fake_addr')
|
||||
else:
|
||||
self.clientIP = self.realClientIP
|
||||
|
||||
def displayMOTD(self):
|
||||
try:
|
||||
self.writeln(self.fs.file_contents('/etc/motd'))
|
||||
except:
|
||||
pass
|
||||
|
||||
# this doesn't seem to be called upon disconnect, so please use
|
||||
# HoneyPotTransport.connectionLost instead
|
||||
def connectionLost(self, reason):
|
||||
pass
|
||||
# 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(HoneyPotCommand):
|
||||
def call(self):
|
||||
print 'Reading txtcmd from "%s"' % txt
|
||||
f = file(txt, 'r')
|
||||
self.write(f.read())
|
||||
f.close()
|
||||
return command_txtcmd
|
||||
|
||||
def getCommand(self, cmd, paths):
|
||||
if not len(cmd.strip()):
|
||||
return None
|
||||
path = None
|
||||
if cmd in self.commands:
|
||||
return self.commands[cmd]
|
||||
if cmd[0] in ('.', '/'):
|
||||
path = self.fs.resolve_path(cmd, self.cwd)
|
||||
if not self.fs.exists(path):
|
||||
return None
|
||||
else:
|
||||
for i in ['%s/%s' % (self.fs.resolve_path(x, self.cwd), cmd) \
|
||||
for x in paths]:
|
||||
if self.fs.exists(i):
|
||||
path = i
|
||||
break
|
||||
txt = os.path.abspath('%s/%s' % \
|
||||
(self.env.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
|
||||
|
||||
def lineReceived(self, line):
|
||||
if len(self.cmdstack):
|
||||
self.cmdstack[-1].lineReceived(line)
|
||||
|
||||
def writeln(self, data):
|
||||
self.terminal.write(data)
|
||||
self.terminal.nextLine()
|
||||
|
||||
def call_command(self, cmd, *args):
|
||||
obj = cmd(self, *args)
|
||||
self.cmdstack.append(obj)
|
||||
obj.start()
|
||||
|
||||
def addInteractor(self, interactor):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
transport.interactors.append(interactor)
|
||||
|
||||
def delInteractor(self, interactor):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
transport.interactors.remove(interactor)
|
||||
|
||||
def uptime(self, reset = None):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
r = time.time() - transport.factory.starttime
|
||||
if reset:
|
||||
transport.factory.starttime = reset
|
||||
return r
|
||||
|
||||
class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLine):
|
||||
|
||||
def __init__(self, user, env):
|
||||
recvline.HistoricRecvLine.__init__(self)
|
||||
HoneyPotBaseProtocol.__init__(self, user, env)
|
||||
|
||||
def connectionMade(self):
|
||||
HoneyPotBaseProtocol.connectionMade(self)
|
||||
recvline.HistoricRecvLine.connectionMade(self)
|
||||
|
||||
self.cmdstack = [HoneyPotShell(self)]
|
||||
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
transport.factory.sessions[transport.transport.sessionno] = self
|
||||
|
||||
self.keyHandlers.update({
|
||||
'\x04': self.handle_CTRL_D,
|
||||
'\x15': self.handle_CTRL_U,
|
||||
'\x03': self.handle_CTRL_C,
|
||||
'\x09': self.handle_TAB,
|
||||
})
|
||||
|
||||
# this doesn't seem to be called upon disconnect, so please use
|
||||
# HoneyPotTransport.connectionLost instead
|
||||
def connectionLost(self, reason):
|
||||
HoneyPotBaseProtocol.connectionLost(self, reason)
|
||||
recvline.HistoricRecvLine.connectionLost(self, reason)
|
||||
|
||||
# Overriding to prevent terminal.reset()
|
||||
def initializeScreen(self):
|
||||
self.setInsertMode()
|
||||
|
||||
def call_command(self, cmd, *args):
|
||||
self.setTypeoverMode()
|
||||
HoneyPotBaseProtocol.call_command(self, cmd, *args)
|
||||
|
||||
def keystrokeReceived(self, keyID, modifier):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
if type(keyID) == type(''):
|
||||
ttylog.ttylog_write(transport.ttylog_file, len(keyID),
|
||||
ttylog.TYPE_INPUT, time.time(), keyID)
|
||||
recvline.HistoricRecvLine.keystrokeReceived(self, keyID, modifier)
|
||||
|
||||
# Easier way to implement password input?
|
||||
def characterReceived(self, ch, moreCharactersComing):
|
||||
if self.mode == 'insert':
|
||||
self.lineBuffer.insert(self.lineBufferIndex, ch)
|
||||
else:
|
||||
self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
|
||||
self.lineBufferIndex += 1
|
||||
if not self.password_input:
|
||||
self.terminal.write(ch)
|
||||
|
||||
def handle_RETURN(self):
|
||||
if len(self.cmdstack) == 1:
|
||||
if self.lineBuffer:
|
||||
self.historyLines.append(''.join(self.lineBuffer))
|
||||
self.historyPosition = len(self.historyLines)
|
||||
return recvline.RecvLine.handle_RETURN(self)
|
||||
|
||||
def handle_CTRL_C(self):
|
||||
self.cmdstack[-1].ctrl_c()
|
||||
|
||||
def handle_CTRL_U(self):
|
||||
for i in range(self.lineBufferIndex):
|
||||
self.terminal.cursorBackward()
|
||||
self.terminal.deleteCharacter()
|
||||
self.lineBuffer = self.lineBuffer[self.lineBufferIndex:]
|
||||
self.lineBufferIndex = 0
|
||||
|
||||
def handle_CTRL_D(self):
|
||||
self.call_command(self.commands['exit'])
|
||||
|
||||
def handle_TAB(self):
|
||||
self.cmdstack[-1].handle_TAB()
|
||||
|
||||
class HoneyPotExecProtocol(HoneyPotBaseProtocol):
|
||||
|
||||
def __init__(self, user, env, execcmd):
|
||||
self.execcmd = execcmd
|
||||
HoneyPotBaseProtocol.__init__(self, user, env)
|
||||
|
||||
def connectionMade(self):
|
||||
HoneyPotBaseProtocol.connectionMade(self)
|
||||
|
||||
self.cmdstack = [HoneyPotShell(self, interactive=False)]
|
||||
|
||||
print 'Running exec command "%s"' % self.execcmd
|
||||
self.cmdstack[0].lineReceived(self.execcmd)
|
||||
|
||||
class LoggingServerProtocol(insults.ServerProtocol):
|
||||
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'),
|
||||
int(random.random() * 10000))
|
||||
print 'Opening TTY log: %s' % transport.ttylog_file
|
||||
ttylog.ttylog_open(transport.ttylog_file, time.time())
|
||||
|
||||
transport.ttylog_open = True
|
||||
|
||||
insults.ServerProtocol.connectionMade(self)
|
||||
|
||||
def write(self, bytes, noLog = False):
|
||||
transport = self.transport.session.conn.transport
|
||||
for i in transport.interactors:
|
||||
i.sessionWrite(bytes)
|
||||
if transport.ttylog_open and not noLog:
|
||||
ttylog.ttylog_write(transport.ttylog_file, len(bytes),
|
||||
ttylog.TYPE_OUTPUT, time.time(), bytes)
|
||||
insults.ServerProtocol.write(self, bytes)
|
||||
|
||||
# this doesn't seem to be called upon disconnect, so please use
|
||||
# HoneyPotTransport.connectionLost instead
|
||||
def connectionLost(self, reason):
|
||||
insults.ServerProtocol.connectionLost(self, reason)
|
||||
|
||||
class HoneyPotSSHSession(session.SSHSession):
|
||||
def request_env(self, data):
|
||||
print 'request_env: %s' % (repr(data))
|
||||
|
||||
class HoneyPotAvatar(avatar.ConchUser):
|
||||
implements(conchinterfaces.ISession)
|
||||
|
||||
def __init__(self, username, env):
|
||||
avatar.ConchUser.__init__(self)
|
||||
self.username = username
|
||||
self.env = env
|
||||
self.channelLookup.update({'session': HoneyPotSSHSession})
|
||||
|
||||
userdb = UserDB()
|
||||
self.uid = self.gid = userdb.getUID(self.username)
|
||||
|
||||
if not self.uid:
|
||||
self.home = '/root'
|
||||
else:
|
||||
self.home = '/home/' + username
|
||||
|
||||
def openShell(self, protocol):
|
||||
serverProtocol = LoggingServerProtocol(
|
||||
HoneyPotInteractiveProtocol, self, self.env)
|
||||
serverProtocol.makeConnection(protocol)
|
||||
protocol.makeConnection(session.wrapProtocol(serverProtocol))
|
||||
|
||||
def getPty(self, terminal, windowSize, attrs):
|
||||
print 'Terminal size: %s %s' % windowSize[0:2]
|
||||
self.windowSize = windowSize
|
||||
return None
|
||||
|
||||
def execCommand(self, protocol, cmd):
|
||||
cfg = config()
|
||||
if not cfg.has_option('honeypot', 'exec_enabled') or \
|
||||
cfg.get('honeypot', 'exec_enabled').lower() not in \
|
||||
('yes', 'true', 'on'):
|
||||
print 'Exec disabled. Not executing command: "%s"' % cmd
|
||||
raise exceptions.NotEnabledException, \
|
||||
'exce_enabled not enabled in configuration file!'
|
||||
return
|
||||
|
||||
print 'exec command: "%s"' % cmd
|
||||
serverProtocol = LoggingServerProtocol(
|
||||
HoneyPotExecProtocol, self, self.env, cmd)
|
||||
serverProtocol.makeConnection(protocol)
|
||||
protocol.makeConnection(session.wrapProtocol(serverProtocol))
|
||||
|
||||
def closed(self):
|
||||
pass
|
||||
|
||||
def eofReceived(self):
|
||||
pass
|
||||
|
||||
def windowChanged(self, windowSize):
|
||||
self.windowSize = windowSize
|
||||
|
||||
class HoneyPotEnvironment(object):
|
||||
def __init__(self):
|
||||
self.cfg = config()
|
||||
|
@ -555,281 +255,4 @@ class HoneyPotEnvironment(object):
|
|||
self.fs = pickle.load(file(
|
||||
self.cfg.get('honeypot', 'filesystem_file'), 'rb'))
|
||||
|
||||
class HoneyPotRealm:
|
||||
implements(portal.IRealm)
|
||||
|
||||
def __init__(self):
|
||||
# I don't know if i'm supposed to keep static stuff here
|
||||
self.env = HoneyPotEnvironment()
|
||||
|
||||
def requestAvatar(self, avatarId, mind, *interfaces):
|
||||
if conchinterfaces.IConchUser in interfaces:
|
||||
return interfaces[0], \
|
||||
HoneyPotAvatar(avatarId, self.env), lambda: None
|
||||
else:
|
||||
raise Exception, "No supported interfaces found."
|
||||
|
||||
class HoneyPotTransport(transport.SSHServerTransport):
|
||||
|
||||
hadVersion = False
|
||||
|
||||
def connectionMade(self):
|
||||
print '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)
|
||||
self.interactors = []
|
||||
self.logintime = time.time()
|
||||
self.ttylog_open = False
|
||||
transport.SSHServerTransport.connectionMade(self)
|
||||
|
||||
def sendKexInit(self):
|
||||
# Don't send key exchange prematurely
|
||||
if not self.gotVersion:
|
||||
return
|
||||
transport.SSHServerTransport.sendKexInit(self)
|
||||
|
||||
def dataReceived(self, data):
|
||||
transport.SSHServerTransport.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:
|
||||
self.sendKexInit()
|
||||
self.hadVersion = True
|
||||
|
||||
def ssh_KEXINIT(self, packet):
|
||||
print 'Remote SSH version: %s' % (self.otherVersionString,)
|
||||
return transport.SSHServerTransport.ssh_KEXINIT(self, packet)
|
||||
|
||||
def lastlogExit(self):
|
||||
starttime = time.strftime('%a %b %d %H:%M',
|
||||
time.localtime(self.logintime))
|
||||
endtime = time.strftime('%H:%M',
|
||||
time.localtime(time.time()))
|
||||
duration = utils.durationHuman(time.time() - self.logintime)
|
||||
clientIP = self.transport.getPeer().host
|
||||
utils.addToLastlog('root\tpts/0\t%s\t%s - %s (%s)' % \
|
||||
(clientIP, starttime, endtime, duration))
|
||||
|
||||
# this seems to be the only reliable place of catching lost connection
|
||||
def connectionLost(self, reason):
|
||||
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
|
||||
transport.SSHServerTransport.connectionLost(self, reason)
|
||||
|
||||
def sendDisconnect(self, reason, desc):
|
||||
"""
|
||||
Workaround for the "bad packet length" error message.
|
||||
|
||||
@param reason: the reason for the disconnect. Should be one of the
|
||||
DISCONNECT_* values.
|
||||
@type reason: C{int}
|
||||
@param desc: a descrption of the reason for the disconnection.
|
||||
@type desc: C{str}
|
||||
"""
|
||||
if not 'bad packet length' in desc:
|
||||
# With python >= 3 we can use super?
|
||||
transport.SSHServerTransport.sendDisconnect(self, reason, desc)
|
||||
else:
|
||||
self.transport.write('Protocol mismatch.\n')
|
||||
log.msg('Disconnecting with error, code %s\nreason: %s' % (reason, desc))
|
||||
self.transport.loseConnection()
|
||||
|
||||
from twisted.conch.ssh.common import NS, getNS
|
||||
class HoneyPotSSHUserAuthServer(userauth.SSHUserAuthServer):
|
||||
def serviceStarted(self):
|
||||
userauth.SSHUserAuthServer.serviceStarted(self)
|
||||
self.bannerSent = False
|
||||
|
||||
def sendBanner(self):
|
||||
if self.bannerSent:
|
||||
return
|
||||
cfg = config()
|
||||
if not cfg.has_option('honeypot', 'banner_file'):
|
||||
return
|
||||
try:
|
||||
data = file(cfg.get('honeypot', 'banner_file')).read()
|
||||
except IOError:
|
||||
print 'Banner file %s does not exist!' % \
|
||||
cfg.get('honeypot', 'banner_file')
|
||||
return
|
||||
if not data or not len(data.strip()):
|
||||
return
|
||||
data = '\r\n'.join(data.splitlines() + [''])
|
||||
self.transport.sendPacket(
|
||||
userauth.MSG_USERAUTH_BANNER, NS(data) + NS('en'))
|
||||
self.bannerSent = True
|
||||
|
||||
def ssh_USERAUTH_REQUEST(self, packet):
|
||||
self.sendBanner()
|
||||
return userauth.SSHUserAuthServer.ssh_USERAUTH_REQUEST(self, packet)
|
||||
|
||||
# As implemented by Kojoney
|
||||
class HoneyPotSSHFactory(factory.SSHFactory):
|
||||
services = {
|
||||
'ssh-userauth': HoneyPotSSHUserAuthServer,
|
||||
'ssh-connection': connection.SSHConnection,
|
||||
}
|
||||
|
||||
# Special delivery to the loggers to avoid scope problems
|
||||
def logDispatch(self, sessionid, msg):
|
||||
for dblog in self.dbloggers:
|
||||
dblog.logDispatch(sessionid, msg)
|
||||
|
||||
def __init__(self):
|
||||
cfg = config()
|
||||
|
||||
# protocol^Wwhatever instances are kept here for the interact feature
|
||||
self.sessions = {}
|
||||
|
||||
# for use by the uptime command
|
||||
self.starttime = time.time()
|
||||
|
||||
# convert old pass.db root passwords
|
||||
passdb_file = '%s/pass.db' % (cfg.get('honeypot', 'data_path'),)
|
||||
if os.path.exists(passdb_file):
|
||||
userdb = UserDB()
|
||||
print 'pass.db deprecated - copying passwords over to userdb.txt'
|
||||
if os.path.exists('%s.bak' % (passdb_file,)):
|
||||
print 'ERROR: %s.bak already exists, skipping conversion!' % \
|
||||
(passdb_file,)
|
||||
else:
|
||||
passdb = anydbm.open(passdb_file, 'c')
|
||||
for p in passdb:
|
||||
userdb.adduser('root', 0, p)
|
||||
passdb.close()
|
||||
os.rename(passdb_file, '%s.bak' % (passdb_file,))
|
||||
print 'pass.db backed up to %s.bak' % (passdb_file,)
|
||||
|
||||
# load db loggers
|
||||
self.dbloggers = []
|
||||
for x in cfg.sections():
|
||||
if not x.startswith('database_'):
|
||||
continue
|
||||
engine = x.split('_')[1]
|
||||
dbengine = 'database_' + engine
|
||||
lcfg = ConfigParser.ConfigParser()
|
||||
lcfg.add_section(dbengine)
|
||||
for i in cfg.options(x):
|
||||
lcfg.set(dbengine, i, cfg.get(x, i))
|
||||
lcfg.add_section('honeypot')
|
||||
for i in cfg.options('honeypot'):
|
||||
lcfg.set('honeypot', i, cfg.get('honeypot', i))
|
||||
print 'Loading dblog engine: %s' % (engine,)
|
||||
dblogger = __import__(
|
||||
'kippo.dblog.%s' % (engine,),
|
||||
globals(), locals(), ['dblog']).DBLogger(lcfg)
|
||||
log.startLoggingWithObserver(dblogger.emit, setStdout=False)
|
||||
self.dbloggers.append(dblogger)
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
cfg = config()
|
||||
|
||||
# FIXME: try to mimic something real 100%
|
||||
t = HoneyPotTransport()
|
||||
|
||||
if cfg.has_option('honeypot', 'ssh_version_string'):
|
||||
t.ourVersionString = cfg.get('honeypot','ssh_version_string')
|
||||
else:
|
||||
t.ourVersionString = "SSH-2.0-OpenSSH_5.1p1 Debian-5"
|
||||
|
||||
t.supportedPublicKeys = self.privateKeys.keys()
|
||||
|
||||
if not self.primes:
|
||||
ske = t.supportedKeyExchanges[:]
|
||||
ske.remove('diffie-hellman-group-exchange-sha1')
|
||||
t.supportedKeyExchanges = ske
|
||||
|
||||
t.factory = self
|
||||
return t
|
||||
|
||||
class HoneypotPasswordChecker:
|
||||
implements(checkers.ICredentialsChecker)
|
||||
|
||||
credentialInterfaces = (credentials.IUsernamePassword,
|
||||
credentials.IPluggableAuthenticationModules)
|
||||
|
||||
def requestAvatarId(self, credentials):
|
||||
if hasattr(credentials, 'password'):
|
||||
if self.checkUserPass(credentials.username, credentials.password):
|
||||
return defer.succeed(credentials.username)
|
||||
else:
|
||||
return defer.fail(error.UnauthorizedLogin())
|
||||
elif hasattr(credentials, 'pamConversion'):
|
||||
return self.checkPamUser(credentials.username,
|
||||
credentials.pamConversion)
|
||||
return defer.fail(error.UnhandledCredentials())
|
||||
|
||||
def checkPamUser(self, username, pamConversion):
|
||||
r = pamConversion((('Password:', 1),))
|
||||
return r.addCallback(self.cbCheckPamUser, username)
|
||||
|
||||
def cbCheckPamUser(self, responses, username):
|
||||
for response, zero in responses:
|
||||
if self.checkUserPass(username, response):
|
||||
return defer.succeed(username)
|
||||
return defer.fail(error.UnauthorizedLogin())
|
||||
|
||||
def checkUserPass(self, username, password):
|
||||
if UserDB().checklogin(username, password):
|
||||
print 'login attempt [%s/%s] succeeded' % (username, password)
|
||||
return True
|
||||
else:
|
||||
print 'login attempt [%s/%s] failed' % (username, password)
|
||||
return False
|
||||
|
||||
def getRSAKeys():
|
||||
cfg = config()
|
||||
public_key = cfg.get('honeypot', 'rsa_public_key')
|
||||
private_key = cfg.get('honeypot', 'rsa_private_key')
|
||||
if not (os.path.exists(public_key) and os.path.exists(private_key)):
|
||||
print "Generating new RSA keypair..."
|
||||
from Crypto.PublicKey import RSA
|
||||
from twisted.python import randbytes
|
||||
KEY_LENGTH = 2048
|
||||
rsaKey = RSA.generate(KEY_LENGTH, randbytes.secureRandom)
|
||||
publicKeyString = keys.Key(rsaKey).public().toString('openssh')
|
||||
privateKeyString = keys.Key(rsaKey).toString('openssh')
|
||||
with file(public_key, 'w+b') as f:
|
||||
f.write(publicKeyString)
|
||||
with file(private_key, 'w+b') as f:
|
||||
f.write(privateKeyString)
|
||||
print "Done."
|
||||
else:
|
||||
with file(public_key) as f:
|
||||
publicKeyString = f.read()
|
||||
with file(private_key) as f:
|
||||
privateKeyString = f.read()
|
||||
return publicKeyString, privateKeyString
|
||||
|
||||
def getDSAKeys():
|
||||
cfg = config()
|
||||
public_key = cfg.get('honeypot', 'dsa_public_key')
|
||||
private_key = cfg.get('honeypot', 'dsa_private_key')
|
||||
if not (os.path.exists(public_key) and os.path.exists(private_key)):
|
||||
print "Generating new DSA keypair..."
|
||||
from Crypto.PublicKey import DSA
|
||||
from twisted.python import randbytes
|
||||
KEY_LENGTH = 1024
|
||||
dsaKey = DSA.generate(KEY_LENGTH, randbytes.secureRandom)
|
||||
publicKeyString = keys.Key(dsaKey).public().toString('openssh')
|
||||
privateKeyString = keys.Key(dsaKey).toString('openssh')
|
||||
with file(public_key, 'w+b') as f:
|
||||
f.write(publicKeyString)
|
||||
with file(private_key, 'w+b') as f:
|
||||
f.write(privateKeyString)
|
||||
print "Done."
|
||||
else:
|
||||
with file(public_key) as f:
|
||||
publicKeyString = f.read()
|
||||
with file(private_key) as f:
|
||||
privateKeyString = f.read()
|
||||
return publicKeyString, privateKeyString
|
||||
|
||||
# vim: set sw=4 et:
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
from twisted.internet import protocol
|
||||
from twisted.conch import telnet, recvline
|
||||
from kippo.core import ttylog
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import struct
|
||||
|
||||
from twisted.conch import recvline
|
||||
from twisted.conch.ssh import transport
|
||||
from twisted.conch.insults import insults
|
||||
from twisted.internet import protocol
|
||||
from copy import deepcopy, copy
|
||||
|
||||
from kippo.core import ttylog, fs
|
||||
from kippo.core.config import config
|
||||
from kippo.core import exceptions
|
||||
from kippo import core
|
||||
|
||||
class HoneyPotBaseProtocol(insults.TerminalProtocol):
|
||||
def __init__(self, user, env):
|
||||
self.user = user
|
||||
self.env = env
|
||||
self.hostname = self.env.cfg.get('honeypot', 'hostname')
|
||||
self.fs = fs.HoneyPotFilesystem(deepcopy(self.env.fs))
|
||||
if self.fs.exists(user.home):
|
||||
self.cwd = user.home
|
||||
else:
|
||||
self.cwd = '/'
|
||||
# commands is also a copy so we can add stuff on the fly
|
||||
self.commands = copy(self.env.commands)
|
||||
self.password_input = False
|
||||
self.cmdstack = []
|
||||
|
||||
def logDispatch(self, msg):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
msg = ':dispatch: ' + msg
|
||||
transport.factory.logDispatch(transport.transport.sessionno, msg)
|
||||
|
||||
def connectionMade(self):
|
||||
self.displayMOTD()
|
||||
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
|
||||
#transport = self.transport.transport.session.conn.transport
|
||||
self.realClientIP = transport.getPeer().address.host
|
||||
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()
|
||||
if cfg.has_option('honeypot', 'fake_addr'):
|
||||
self.clientIP = cfg.get('honeypot', 'fake_addr')
|
||||
else:
|
||||
self.clientIP = self.realClientIP
|
||||
|
||||
def displayMOTD(self):
|
||||
try:
|
||||
self.writeln(self.fs.file_contents('/etc/motd'))
|
||||
except:
|
||||
pass
|
||||
|
||||
# this doesn't seem to be called upon disconnect, so please use
|
||||
# HoneyPotTransport.connectionLost instead
|
||||
def connectionLost(self, reason):
|
||||
pass
|
||||
# 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(HoneyPotCommand):
|
||||
def call(self):
|
||||
print 'Reading txtcmd from "%s"' % txt
|
||||
f = file(txt, 'r')
|
||||
self.write(f.read())
|
||||
f.close()
|
||||
return command_txtcmd
|
||||
|
||||
def getCommand(self, cmd, paths):
|
||||
if not len(cmd.strip()):
|
||||
return None
|
||||
path = None
|
||||
if cmd in self.commands:
|
||||
return self.commands[cmd]
|
||||
if cmd[0] in ('.', '/'):
|
||||
path = self.fs.resolve_path(cmd, self.cwd)
|
||||
if not self.fs.exists(path):
|
||||
return None
|
||||
else:
|
||||
for i in ['%s/%s' % (self.fs.resolve_path(x, self.cwd), cmd) \
|
||||
for x in paths]:
|
||||
if self.fs.exists(i):
|
||||
path = i
|
||||
break
|
||||
txt = os.path.abspath('%s/%s' % \
|
||||
(self.env.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
|
||||
|
||||
def lineReceived(self, line):
|
||||
if len(self.cmdstack):
|
||||
self.cmdstack[-1].lineReceived(line)
|
||||
|
||||
def writeln(self, data):
|
||||
self.terminal.write(data)
|
||||
self.terminal.nextLine()
|
||||
|
||||
def call_command(self, cmd, *args):
|
||||
obj = cmd(self, *args)
|
||||
self.cmdstack.append(obj)
|
||||
obj.start()
|
||||
|
||||
def addInteractor(self, interactor):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
transport.interactors.append(interactor)
|
||||
|
||||
def delInteractor(self, interactor):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
transport.interactors.remove(interactor)
|
||||
|
||||
def uptime(self, reset = None):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
r = time.time() - transport.factory.starttime
|
||||
if reset:
|
||||
transport.factory.starttime = reset
|
||||
return r
|
||||
|
||||
class HoneyPotExecProtocol(HoneyPotBaseProtocol):
|
||||
|
||||
def __init__(self, user, env, execcmd):
|
||||
self.execcmd = execcmd
|
||||
HoneyPotBaseProtocol.__init__(self, user, env)
|
||||
|
||||
def connectionMade(self):
|
||||
HoneyPotBaseProtocol.connectionMade(self)
|
||||
|
||||
self.cmdstack = [core.honeypot.HoneyPotShell(self, interactive=False)]
|
||||
|
||||
print 'Running exec command "%s"' % self.execcmd
|
||||
self.cmdstack[0].lineReceived(self.execcmd)
|
||||
|
||||
class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLine):
|
||||
|
||||
def __init__(self, user, env):
|
||||
recvline.HistoricRecvLine.__init__(self)
|
||||
HoneyPotBaseProtocol.__init__(self, user, env)
|
||||
|
||||
def connectionMade(self):
|
||||
HoneyPotBaseProtocol.connectionMade(self)
|
||||
recvline.HistoricRecvLine.connectionMade(self)
|
||||
|
||||
self.cmdstack = [core.honeypot.HoneyPotShell(self)]
|
||||
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
transport.factory.sessions[transport.transport.sessionno] = self
|
||||
|
||||
self.keyHandlers.update({
|
||||
'\x04': self.handle_CTRL_D,
|
||||
'\x15': self.handle_CTRL_U,
|
||||
'\x03': self.handle_CTRL_C,
|
||||
'\x09': self.handle_TAB,
|
||||
})
|
||||
|
||||
# this doesn't seem to be called upon disconnect, so please use
|
||||
# HoneyPotTransport.connectionLost instead
|
||||
def connectionLost(self, reason):
|
||||
HoneyPotBaseProtocol.connectionLost(self, reason)
|
||||
recvline.HistoricRecvLine.connectionLost(self, reason)
|
||||
|
||||
# Overriding to prevent terminal.reset()
|
||||
def initializeScreen(self):
|
||||
self.setInsertMode()
|
||||
|
||||
def call_command(self, cmd, *args):
|
||||
self.setTypeoverMode()
|
||||
HoneyPotBaseProtocol.call_command(self, cmd, *args)
|
||||
|
||||
def keystrokeReceived(self, keyID, modifier):
|
||||
transport = self.terminal.transport.session.conn.transport
|
||||
if type(keyID) == type(''):
|
||||
ttylog.ttylog_write(transport.ttylog_file, len(keyID),
|
||||
ttylog.TYPE_INPUT, time.time(), keyID)
|
||||
recvline.HistoricRecvLine.keystrokeReceived(self, keyID, modifier)
|
||||
|
||||
# Easier way to implement password input?
|
||||
def characterReceived(self, ch, moreCharactersComing):
|
||||
if self.mode == 'insert':
|
||||
self.lineBuffer.insert(self.lineBufferIndex, ch)
|
||||
else:
|
||||
self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
|
||||
self.lineBufferIndex += 1
|
||||
if not self.password_input:
|
||||
self.terminal.write(ch)
|
||||
|
||||
def handle_RETURN(self):
|
||||
if len(self.cmdstack) == 1:
|
||||
if self.lineBuffer:
|
||||
self.historyLines.append(''.join(self.lineBuffer))
|
||||
self.historyPosition = len(self.historyLines)
|
||||
return recvline.RecvLine.handle_RETURN(self)
|
||||
|
||||
def handle_CTRL_C(self):
|
||||
self.cmdstack[-1].ctrl_c()
|
||||
|
||||
def handle_CTRL_U(self):
|
||||
for i in range(self.lineBufferIndex):
|
||||
self.terminal.cursorBackward()
|
||||
self.terminal.deleteCharacter()
|
||||
self.lineBuffer = self.lineBuffer[self.lineBufferIndex:]
|
||||
self.lineBufferIndex = 0
|
||||
|
||||
def handle_CTRL_D(self):
|
||||
self.call_command(self.commands['exit'])
|
||||
|
||||
def handle_TAB(self):
|
||||
self.cmdstack[-1].handle_TAB()
|
||||
|
||||
class LoggingServerProtocol(insults.ServerProtocol):
|
||||
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'),
|
||||
int(random.random() * 10000))
|
||||
print 'Opening TTY log: %s' % transport.ttylog_file
|
||||
ttylog.ttylog_open(transport.ttylog_file, time.time())
|
||||
|
||||
transport.ttylog_open = True
|
||||
|
||||
insults.ServerProtocol.connectionMade(self)
|
||||
|
||||
def write(self, bytes, noLog = False):
|
||||
transport = self.transport.session.conn.transport
|
||||
for i in transport.interactors:
|
||||
i.sessionWrite(bytes)
|
||||
if transport.ttylog_open and not noLog:
|
||||
ttylog.ttylog_write(transport.ttylog_file, len(bytes),
|
||||
ttylog.TYPE_OUTPUT, time.time(), bytes)
|
||||
insults.ServerProtocol.write(self, bytes)
|
||||
|
||||
# this doesn't seem to be called upon disconnect, so please use
|
||||
# HoneyPotTransport.connectionLost instead
|
||||
def connectionLost(self, reason):
|
||||
insults.ServerProtocol.connectionLost(self, reason)
|
||||
|
||||
# vim: set sw=4 et:
|
|
@ -0,0 +1,307 @@
|
|||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
import twisted
|
||||
from twisted.cred import portal
|
||||
from twisted.conch import avatar, interfaces as conchinterfaces
|
||||
from twisted.conch.ssh import factory, userauth, connection, keys, session, transport
|
||||
from twisted.python import log
|
||||
from zope.interface import implements
|
||||
|
||||
import os
|
||||
import time
|
||||
import ConfigParser
|
||||
|
||||
from kippo.core import ttylog, utils
|
||||
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
|
||||
|
||||
from twisted.conch.ssh.common import NS, getNS
|
||||
class HoneyPotSSHUserAuthServer(userauth.SSHUserAuthServer):
|
||||
def serviceStarted(self):
|
||||
userauth.SSHUserAuthServer.serviceStarted(self)
|
||||
self.bannerSent = False
|
||||
|
||||
def sendBanner(self):
|
||||
if self.bannerSent:
|
||||
return
|
||||
cfg = config()
|
||||
if not cfg.has_option('honeypot', 'banner_file'):
|
||||
return
|
||||
try:
|
||||
data = file(cfg.get('honeypot', 'banner_file')).read()
|
||||
except IOError:
|
||||
print 'Banner file %s does not exist!' % \
|
||||
cfg.get('honeypot', 'banner_file')
|
||||
return
|
||||
if not data or not len(data.strip()):
|
||||
return
|
||||
data = '\r\n'.join(data.splitlines() + [''])
|
||||
self.transport.sendPacket(
|
||||
userauth.MSG_USERAUTH_BANNER, NS(data) + NS('en'))
|
||||
self.bannerSent = True
|
||||
|
||||
def ssh_USERAUTH_REQUEST(self, packet):
|
||||
self.sendBanner()
|
||||
return userauth.SSHUserAuthServer.ssh_USERAUTH_REQUEST(self, packet)
|
||||
|
||||
# As implemented by Kojoney
|
||||
class HoneyPotSSHFactory(factory.SSHFactory):
|
||||
services = {
|
||||
'ssh-userauth': HoneyPotSSHUserAuthServer,
|
||||
'ssh-connection': connection.SSHConnection,
|
||||
}
|
||||
|
||||
# Special delivery to the loggers to avoid scope problems
|
||||
def logDispatch(self, sessionid, msg):
|
||||
for dblog in self.dbloggers:
|
||||
dblog.logDispatch(sessionid, msg)
|
||||
|
||||
def __init__(self):
|
||||
cfg = config()
|
||||
|
||||
# protocol^Wwhatever instances are kept here for the interact feature
|
||||
self.sessions = {}
|
||||
|
||||
# for use by the uptime command
|
||||
self.starttime = time.time()
|
||||
|
||||
# load db loggers
|
||||
self.dbloggers = []
|
||||
for x in cfg.sections():
|
||||
if not x.startswith('database_'):
|
||||
continue
|
||||
engine = x.split('_')[1]
|
||||
dbengine = 'database_' + engine
|
||||
lcfg = ConfigParser.ConfigParser()
|
||||
lcfg.add_section(dbengine)
|
||||
for i in cfg.options(x):
|
||||
lcfg.set(dbengine, i, cfg.get(x, i))
|
||||
lcfg.add_section('honeypot')
|
||||
for i in cfg.options('honeypot'):
|
||||
lcfg.set('honeypot', i, cfg.get('honeypot', i))
|
||||
print 'Loading dblog engine: %s' % (engine,)
|
||||
dblogger = __import__(
|
||||
'kippo.dblog.%s' % (engine,),
|
||||
globals(), locals(), ['dblog']).DBLogger(lcfg)
|
||||
log.startLoggingWithObserver(dblogger.emit, setStdout=False)
|
||||
self.dbloggers.append(dblogger)
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
cfg = config()
|
||||
|
||||
# FIXME: try to mimic something real 100%
|
||||
t = HoneyPotTransport()
|
||||
|
||||
if cfg.has_option('honeypot', 'ssh_version_string'):
|
||||
t.ourVersionString = cfg.get('honeypot','ssh_version_string')
|
||||
else:
|
||||
t.ourVersionString = "SSH-2.0-OpenSSH_5.1p1 Debian-5"
|
||||
|
||||
t.supportedPublicKeys = self.privateKeys.keys()
|
||||
|
||||
if not self.primes:
|
||||
ske = t.supportedKeyExchanges[:]
|
||||
ske.remove('diffie-hellman-group-exchange-sha1')
|
||||
t.supportedKeyExchanges = ske
|
||||
|
||||
t.factory = self
|
||||
return t
|
||||
|
||||
class HoneyPotRealm:
|
||||
implements(portal.IRealm)
|
||||
|
||||
def __init__(self):
|
||||
# I don't know if i'm supposed to keep static stuff here
|
||||
self.env = core.honeypot.HoneyPotEnvironment()
|
||||
|
||||
def requestAvatar(self, avatarId, mind, *interfaces):
|
||||
if conchinterfaces.IConchUser in interfaces:
|
||||
return interfaces[0], \
|
||||
HoneyPotAvatar(avatarId, self.env), lambda: None
|
||||
else:
|
||||
raise Exception, "No supported interfaces found."
|
||||
|
||||
class HoneyPotTransport(transport.SSHServerTransport):
|
||||
|
||||
hadVersion = False
|
||||
|
||||
def connectionMade(self):
|
||||
print '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)
|
||||
self.interactors = []
|
||||
self.logintime = time.time()
|
||||
self.ttylog_open = False
|
||||
transport.SSHServerTransport.connectionMade(self)
|
||||
|
||||
def sendKexInit(self):
|
||||
# Don't send key exchange prematurely
|
||||
if not self.gotVersion:
|
||||
return
|
||||
transport.SSHServerTransport.sendKexInit(self)
|
||||
|
||||
def dataReceived(self, data):
|
||||
transport.SSHServerTransport.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:
|
||||
self.sendKexInit()
|
||||
self.hadVersion = True
|
||||
|
||||
def ssh_KEXINIT(self, packet):
|
||||
print 'Remote SSH version: %s' % (self.otherVersionString,)
|
||||
return transport.SSHServerTransport.ssh_KEXINIT(self, packet)
|
||||
|
||||
def lastlogExit(self):
|
||||
starttime = time.strftime('%a %b %d %H:%M',
|
||||
time.localtime(self.logintime))
|
||||
endtime = time.strftime('%H:%M',
|
||||
time.localtime(time.time()))
|
||||
duration = utils.durationHuman(time.time() - self.logintime)
|
||||
clientIP = self.transport.getPeer().host
|
||||
utils.addToLastlog('root\tpts/0\t%s\t%s - %s (%s)' % \
|
||||
(clientIP, starttime, endtime, duration))
|
||||
|
||||
# this seems to be the only reliable place of catching lost connection
|
||||
def connectionLost(self, reason):
|
||||
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
|
||||
transport.SSHServerTransport.connectionLost(self, reason)
|
||||
|
||||
def sendDisconnect(self, reason, desc):
|
||||
"""
|
||||
Workaround for the "bad packet length" error message.
|
||||
|
||||
@param reason: the reason for the disconnect. Should be one of the
|
||||
DISCONNECT_* values.
|
||||
@type reason: C{int}
|
||||
@param desc: a descrption of the reason for the disconnection.
|
||||
@type desc: C{str}
|
||||
"""
|
||||
if not 'bad packet length' in desc:
|
||||
# With python >= 3 we can use super?
|
||||
transport.SSHServerTransport.sendDisconnect(self, reason, desc)
|
||||
else:
|
||||
self.transport.write('Protocol mismatch.\n')
|
||||
log.msg('Disconnecting with error, code %s\nreason: %s' % \
|
||||
(reason, desc))
|
||||
self.transport.loseConnection()
|
||||
|
||||
class HoneyPotSSHSession(session.SSHSession):
|
||||
def request_env(self, data):
|
||||
print 'request_env: %s' % (repr(data))
|
||||
|
||||
class HoneyPotAvatar(avatar.ConchUser):
|
||||
implements(conchinterfaces.ISession)
|
||||
|
||||
def __init__(self, username, env):
|
||||
avatar.ConchUser.__init__(self)
|
||||
self.username = username
|
||||
self.env = env
|
||||
self.channelLookup.update({'session': HoneyPotSSHSession})
|
||||
|
||||
userdb = core.auth.UserDB()
|
||||
self.uid = self.gid = 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 getPty(self, terminal, windowSize, attrs):
|
||||
print 'Terminal size: %s %s' % windowSize[0:2]
|
||||
self.windowSize = windowSize
|
||||
return None
|
||||
|
||||
def execCommand(self, protocol, cmd):
|
||||
cfg = config()
|
||||
if not cfg.has_option('honeypot', 'exec_enabled') or \
|
||||
cfg.get('honeypot', 'exec_enabled').lower() not in \
|
||||
('yes', 'true', 'on'):
|
||||
print 'Exec disabled. Not executing command: "%s"' % cmd
|
||||
raise core.exceptions.NotEnabledException, \
|
||||
'exec_enabled not enabled in configuration file!'
|
||||
return
|
||||
|
||||
print '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))
|
||||
|
||||
def closed(self):
|
||||
pass
|
||||
|
||||
def eofReceived(self):
|
||||
pass
|
||||
|
||||
def windowChanged(self, windowSize):
|
||||
self.windowSize = windowSize
|
||||
|
||||
def getRSAKeys():
|
||||
cfg = config()
|
||||
public_key = cfg.get('honeypot', 'rsa_public_key')
|
||||
private_key = cfg.get('honeypot', 'rsa_private_key')
|
||||
if not (os.path.exists(public_key) and os.path.exists(private_key)):
|
||||
print "Generating new RSA keypair..."
|
||||
from Crypto.PublicKey import RSA
|
||||
from twisted.python import randbytes
|
||||
KEY_LENGTH = 2048
|
||||
rsaKey = RSA.generate(KEY_LENGTH, randbytes.secureRandom)
|
||||
publicKeyString = keys.Key(rsaKey).public().toString('openssh')
|
||||
privateKeyString = keys.Key(rsaKey).toString('openssh')
|
||||
with file(public_key, 'w+b') as f:
|
||||
f.write(publicKeyString)
|
||||
with file(private_key, 'w+b') as f:
|
||||
f.write(privateKeyString)
|
||||
print "Done."
|
||||
else:
|
||||
with file(public_key) as f:
|
||||
publicKeyString = f.read()
|
||||
with file(private_key) as f:
|
||||
privateKeyString = f.read()
|
||||
return publicKeyString, privateKeyString
|
||||
|
||||
def getDSAKeys():
|
||||
cfg = config()
|
||||
public_key = cfg.get('honeypot', 'dsa_public_key')
|
||||
private_key = cfg.get('honeypot', 'dsa_private_key')
|
||||
if not (os.path.exists(public_key) and os.path.exists(private_key)):
|
||||
print "Generating new DSA keypair..."
|
||||
from Crypto.PublicKey import DSA
|
||||
from twisted.python import randbytes
|
||||
KEY_LENGTH = 1024
|
||||
dsaKey = DSA.generate(KEY_LENGTH, randbytes.secureRandom)
|
||||
publicKeyString = keys.Key(dsaKey).public().toString('openssh')
|
||||
privateKeyString = keys.Key(dsaKey).toString('openssh')
|
||||
with file(public_key, 'w+b') as f:
|
||||
f.write(publicKeyString)
|
||||
with file(private_key, 'w+b') as f:
|
||||
f.write(privateKeyString)
|
||||
print "Done."
|
||||
else:
|
||||
with file(public_key) as f:
|
||||
publicKeyString = f.read()
|
||||
with file(private_key) as f:
|
||||
privateKeyString = f.read()
|
||||
return publicKeyString, privateKeyString
|
||||
|
||||
# vim: set et sw=4 et:
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
# Should be compatible with user mode linux
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2010 Upi Tamminen <desaster@gmail.com>
|
||||
# Copyright (c) 2010-2014 Upi Tamminen <desaster@gmail.com>
|
||||
# See the COPYRIGHT file for more information
|
||||
|
||||
import time, anydbm
|
||||
|
|
Loading…
Reference in New Issue