diff --git a/data/userdb.txt b/data/userdb.txt new file mode 100644 index 0000000..decb76c --- /dev/null +++ b/data/userdb.txt @@ -0,0 +1 @@ +root:0:123456 diff --git a/kippo.cfg.dist b/kippo.cfg.dist index 599185a..e2c568a 100644 --- a/kippo.cfg.dist +++ b/kippo.cfg.dist @@ -74,11 +74,9 @@ txtcmds_path = txtcmds public_key = public.key private_key = private.key -# Initial root password. Future passwords will be stored in -# {data_path}/pass.db -# -# (default: 123456) -password = 123456 +# Initial root password. NO LONGER USED! +# Instead, see {data_path}/userdb.txt +#password = 123456 # IP address to bind to when opening outgoing connections. Used exclusively by # the wget command. diff --git a/kippo/commands/base.py b/kippo/commands/base.py index fdd05d5..6eeb990 100644 --- a/kippo/commands/base.py +++ b/kippo/commands/base.py @@ -5,6 +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 commands = {} @@ -24,8 +25,9 @@ class command_w(HoneyPotCommand): self.writeln(' %s up 14 days, 3:53, 1 user, load average: 0.08, 0.02, 0.01' % \ time.strftime('%H:%M:%S')) self.writeln('USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT') - self.writeln('root pts/0 %s %s 0.00s 0.00s 0.00s w' % \ - (self.honeypot.clientIP[:17].ljust(17), + self.writeln('%-8s pts/0 %s %s 0.00s 0.00s 0.00s w' % \ + (self.honeypot.user.username, + self.honeypot.clientIP[:17].ljust(17), time.strftime('%H:%M', time.localtime(self.honeypot.logintime)))) commands['/usr/bin/w'] = command_w commands['/usr/bin/who'] = command_w @@ -125,7 +127,9 @@ commands['/bin/ps'] = command_ps class command_id(HoneyPotCommand): def call(self): - self.writeln('uid=0(root) gid=0(root) groups=0(root)') + u = self.honeypot.user + self.writeln('uid=%d(%s) gid=%d(%s) groups=%d(%s)' % \ + (u.uid, u.username, u.gid, u.username, u.gid, u.username)) commands['/usr/bin/id'] = command_id class command_passwd(HoneyPotCommand): @@ -133,18 +137,23 @@ class command_passwd(HoneyPotCommand): self.write('Enter new UNIX password: ') self.honeypot.password_input = True self.callbacks = [self.ask_again, self.finish] + self.passwd = None - def ask_again(self): + def ask_again(self, line): + self.passwd = line self.write('Retype new UNIX password: ') - def finish(self): + def finish(self, line): self.honeypot.password_input = False - data_path = self.honeypot.env.cfg.get('honeypot', 'data_path') - passdb = anydbm.open('%s/pass.db' % (data_path,), 'c') - if len(self.password) and self.password not in passdb: - passdb[self.password] = '' - passdb.close() + if line != self.passwd: + self.writeln('Sorry, passwords do not match') + self.exit() + return + + userdb = UserDB() + userdb.adduser(self.honeypot.user.username, + self.honeypot.user.uid, self.passwd) self.writeln('passwd: password updated successfully') self.exit() @@ -152,7 +161,7 @@ class command_passwd(HoneyPotCommand): def lineReceived(self, line): print 'INPUT (passwd):', line self.password = line.strip() - self.callbacks.pop(0)() + self.callbacks.pop(0)(line) commands['/usr/bin/passwd'] = command_passwd class command_shutdown(HoneyPotCommand): diff --git a/kippo/commands/fs.py b/kippo/commands/fs.py index 3501ee5..c24c10e 100644 --- a/kippo/commands/fs.py +++ b/kippo/commands/fs.py @@ -26,7 +26,7 @@ commands['/bin/cat'] = command_cat class command_cd(HoneyPotCommand): def call(self): if not self.args: - path = '/root' + path = self.honeypot.user.home else: path = self.args[0] try: diff --git a/kippo/core/honeypot.py b/kippo/core/honeypot.py index ef35fc4..7024c9b 100644 --- a/kippo/core/honeypot.py +++ b/kippo/core/honeypot.py @@ -14,6 +14,7 @@ from copy import deepcopy, copy import sys, os, random, pickle, time, stat, shlex, anydbm from kippo.core import ttylog, fs, utils +from kippo.core.userdb import UserDB from kippo.core.config import config import commands @@ -133,10 +134,19 @@ class HoneyPotShell(object): self.runCommand() def showPrompt(self): - prompt = '%s:%%(path)s# ' % self.honeypot.hostname + if not self.honeypot.user.uid: + prompt = '%s:%%(path)s# ' % self.honeypot.hostname + else: + prompt = '%s:%%(path)s$ ' % self.honeypot.hostname + path = self.honeypot.cwd - if path == '/root': + homelen = len(self.honeypot.user.home) + if path == self.honeypot.user.home: path = '~' + elif len(path) > (homelen+1) and \ + path[:(homelen+1)] == self.honeypot.user.home + '/': + path = '~' + path[homelen:] + attrs = {'path': path} self.honeypot.terminal.write(prompt % attrs) @@ -223,7 +233,7 @@ class HoneyPotProtocol(recvline.HistoricRecvLine): def __init__(self, user, env): self.user = user self.env = env - self.cwd = '/root' + self.cwd = user.home self.hostname = self.env.cfg.get('honeypot', 'hostname') self.fs = fs.HoneyPotFilesystem(deepcopy(self.env.fs)) # commands is also a copy so we can add stuff on the fly @@ -406,6 +416,14 @@ class HoneyPotAvatar(avatar.ConchUser): self.env = env self.channelLookup.update({'session':session.SSHSession}) + 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(HoneyPotProtocol, self, self.env) serverProtocol.makeConnection(protocol) @@ -476,6 +494,24 @@ class HoneyPotSSHFactory(factory.SSHFactory): def __init__(self): cfg = config() + + # 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 for x in cfg.sections(): if not x.startswith('database_'): continue @@ -532,20 +568,12 @@ class HoneypotPasswordChecker: return defer.fail(error.UnauthorizedLogin()) def checkUserPass(self, username, password): - cfg = config() - data_path = cfg.get('honeypot', 'data_path') - passdb = anydbm.open('%s/pass.db' % (data_path,), 'c') - success = False - if username == 'root' and password == cfg.get('honeypot', 'password'): - success = True - elif username == 'root' and password in passdb: - success = True - passdb.close() - if success: + 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 success + return False def getRSAKeys(): cfg = config() diff --git a/kippo/core/userdb.py b/kippo/core/userdb.py new file mode 100644 index 0000000..c56adc9 --- /dev/null +++ b/kippo/core/userdb.py @@ -0,0 +1,99 @@ +# +# userdb.py for kippo +# by Walter de Jong +# +# adopted and further modified by Upi Tamminen +# + +from kippo.core.config import config +import os +import string + +class UserDB: + def __init__(self): + self.userdb = [] + self.load() + + def load(self): + '''load the user db''' + + userdb_file = '%s/userdb.txt' % \ + (config().get('honeypot', 'data_path'),) + + f = open(userdb_file, 'r') + while True: + line = f.readline() + if not line: + break + + line = string.strip(line) + if not line: + continue + + (login, uid_str, passwd) = string.split(line, ':') + + uid = 0 + try: + uid = int(uid_str) + except ValueError: + uid = 1001 + + self.userdb.append((login, uid, passwd)) + + f.close() + + def save(self): + '''save the user db''' + + userdb_file = '%s/userdb.txt' % \ + (config().get('honeypot', 'data_path'),) + + # Note: this is subject to races between kippo instances, but hey ... + f = open(userdb_file, 'w') + for (login, uid, passwd) in self.userdb: + f.write('%s:%d:%s\n' % (login, uid, passwd)) + f.close() + + def checklogin(self, thelogin, thepasswd): + '''check entered username/password against database''' + '''note that it allows multiple passwords for a single username''' + + for (login, uid, passwd) in self.userdb: + if login == thelogin and passwd == thepasswd: + return True + return False + + def user_exists(self, thelogin): + for (login, uid, passwd) in self.userdb: + if login == thelogin: + return True + return False + + def user_password_exists(self, thelogin, thepasswd): + for (login, uid, passwd) in self.userdb: + if login == thelogin and passwd == thepasswd: + return True + return False + + def getUID(self, loginname): + for (login, uid, passwd) in self.userdb: + if loginname == login: + return uid + return 1001 + + def allocUID(self): + '''allocate the next UID''' + + min_uid = 0 + for (login, uid, passwd) in self.userdb: + if uid > min_uid: + min_uid = uid + return min_uid + 1 + + def adduser(self, login, uid, passwd): + if self.user_password_exists(login, passwd): + return + self.userdb.append((login, uid, passwd)) + self.save() + +# vim: set sw=4 et: