mirror of
https://github.com/aljazceru/cowrie.git
synced 2025-12-18 22:44:29 +01:00
move core/ and commands/ to kippo/
git-svn-id: https://kippo.googlecode.com/svn/trunk@50 951d7100-d841-11de-b865-b3884708a8e2
This commit is contained in:
@@ -13,8 +13,8 @@ from twisted.internet import reactor, defer
|
|||||||
from twisted.application import internet, service
|
from twisted.application import internet, service
|
||||||
from twisted.cred import portal
|
from twisted.cred import portal
|
||||||
from twisted.conch.ssh import factory, keys
|
from twisted.conch.ssh import factory, keys
|
||||||
from core import honeypot
|
from kippo.core import honeypot
|
||||||
from core.config import config
|
from kippo.core.config import config
|
||||||
|
|
||||||
factory = honeypot.HoneyPotSSHFactory()
|
factory = honeypot.HoneyPotSSHFactory()
|
||||||
factory.portal = portal.Portal(honeypot.HoneyPotRealm())
|
factory.portal = portal.Portal(honeypot.HoneyPotRealm())
|
||||||
|
|||||||
0
kippo/__init__.py
Normal file
0
kippo/__init__.py
Normal file
12
kippo/commands/__init__.py
Normal file
12
kippo/commands/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'base',
|
||||||
|
'ls',
|
||||||
|
'ping',
|
||||||
|
'ssh',
|
||||||
|
'tar',
|
||||||
|
'wget',
|
||||||
|
'dice',
|
||||||
|
]
|
||||||
293
kippo/commands/base.py
Normal file
293
kippo/commands/base.py
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
import os, time
|
||||||
|
from kippo.core.honeypot import HoneyPotCommand
|
||||||
|
from kippo.core.fs import *
|
||||||
|
from twisted.internet import reactor
|
||||||
|
import config
|
||||||
|
|
||||||
|
commands = {}
|
||||||
|
|
||||||
|
class command_whoami(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
self.writeln(self.honeypot.user.username)
|
||||||
|
commands['/usr/bin/whoami'] = command_whoami
|
||||||
|
|
||||||
|
class command_cat(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
for arg in self.args:
|
||||||
|
path = self.fs.resolve_path(arg, self.honeypot.cwd)
|
||||||
|
if not path or not self.fs.exists(path):
|
||||||
|
self.writeln('bash: cat: %s: No such file or directory' % arg)
|
||||||
|
return
|
||||||
|
f = self.fs.getfile(path)
|
||||||
|
|
||||||
|
realfile = self.fs.realfile(f,
|
||||||
|
'%s/%s' % (config.contents_path, path))
|
||||||
|
if realfile:
|
||||||
|
f = file(realfile, 'rb')
|
||||||
|
self.write(f.read())
|
||||||
|
f.close()
|
||||||
|
commands['/bin/cat'] = command_cat
|
||||||
|
|
||||||
|
class command_cd(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
if not self.args:
|
||||||
|
path = '/root'
|
||||||
|
else:
|
||||||
|
path = self.args[0]
|
||||||
|
try:
|
||||||
|
newpath = self.fs.resolve_path(path, self.honeypot.cwd)
|
||||||
|
newdir = self.fs.get_path(newpath)
|
||||||
|
except IndexError:
|
||||||
|
newdir = None
|
||||||
|
if newdir is None:
|
||||||
|
self.writeln('bash: cd: %s: No such file or directory' % path)
|
||||||
|
return
|
||||||
|
if not self.fs.is_dir(newpath):
|
||||||
|
self.writeln('-bash: cd: %s: Not a directory' % path)
|
||||||
|
return
|
||||||
|
self.honeypot.cwd = newpath
|
||||||
|
commands['cd'] = command_cd
|
||||||
|
|
||||||
|
class command_rm(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
for f in self.args:
|
||||||
|
path = self.fs.resolve_path(f, self.honeypot.cwd)
|
||||||
|
try:
|
||||||
|
dir = self.fs.get_path('/'.join(path.split('/')[:-1]))
|
||||||
|
except IndexError:
|
||||||
|
self.writeln(
|
||||||
|
'rm: cannot remove `%s\': No such file or directory' % f)
|
||||||
|
continue
|
||||||
|
basename = path.split('/')[-1]
|
||||||
|
contents = [x for x in dir]
|
||||||
|
for i in dir[:]:
|
||||||
|
if i[A_NAME] == basename:
|
||||||
|
if i[A_TYPE] == T_DIR:
|
||||||
|
self.writeln(
|
||||||
|
'rm: cannot remove `%s\': Is a directory' % \
|
||||||
|
i[A_NAME])
|
||||||
|
else:
|
||||||
|
dir.remove(i)
|
||||||
|
commands['/bin/rm'] = command_rm
|
||||||
|
|
||||||
|
class command_mkdir(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
for f in self.args:
|
||||||
|
path = self.fs.resolve_path(f, self.honeypot.cwd)
|
||||||
|
if self.fs.exists(path):
|
||||||
|
self.writeln(
|
||||||
|
'mkdir: cannot create directory `%s\': File exists' % f)
|
||||||
|
return
|
||||||
|
ok = self.fs.mkdir(path, 0, 0, 4096, 16877)
|
||||||
|
if not ok:
|
||||||
|
self.writeln(
|
||||||
|
'mkdir: cannot create directory `%s\': ' % f + \
|
||||||
|
'No such file or directory')
|
||||||
|
return
|
||||||
|
commands['/bin/mkdir'] = command_mkdir
|
||||||
|
|
||||||
|
class command_rmdir(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
for f in self.args:
|
||||||
|
path = self.fs.resolve_path(f, self.honeypot.cwd)
|
||||||
|
if len(self.fs.get_path(path)):
|
||||||
|
self.writeln(
|
||||||
|
'rmdir: failed to remove `%s\': Directory not empty' % f)
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
dir = self.fs.get_path('/'.join(path.split('/')[:-1]))
|
||||||
|
except IndexError:
|
||||||
|
dir = None
|
||||||
|
if not dir or f not in [x[A_NAME] for x in dir]:
|
||||||
|
self.writeln(
|
||||||
|
'rmdir: failed to remove `%s\': ' % f + \
|
||||||
|
'No such file or directory')
|
||||||
|
continue
|
||||||
|
for i in dir[:]:
|
||||||
|
if i[A_NAME] == f:
|
||||||
|
dir.remove(i)
|
||||||
|
commands['/bin/rmdir'] = command_rmdir
|
||||||
|
|
||||||
|
class command_uptime(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
self.writeln(
|
||||||
|
' %s up 14 days, 3:53, 0 users, load average: 0.08, 0.02, 0.01' % \
|
||||||
|
time.strftime('%H:%M:%S'))
|
||||||
|
commands['/usr/bin/uptime'] = command_uptime
|
||||||
|
|
||||||
|
class command_w(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
self.writeln(
|
||||||
|
' %s up 14 days, 3:53, 0 users, load average: 0.08, 0.02, 0.01' % \
|
||||||
|
time.strftime('%H:%M:%S'))
|
||||||
|
self.writeln('USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT')
|
||||||
|
commands['/usr/bin/w'] = command_w
|
||||||
|
commands['/usr/bin/who'] = command_w
|
||||||
|
|
||||||
|
class command_echo(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
self.writeln(' '.join(self.args))
|
||||||
|
commands['/bin/echo'] = command_echo
|
||||||
|
|
||||||
|
class command_exit(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
#self.honeypot.terminal.loseConnection()
|
||||||
|
self.honeypot.terminal.reset()
|
||||||
|
self.writeln('Connection to server closed.')
|
||||||
|
self.honeypot.hostname = 'localhost'
|
||||||
|
commands['exit'] = command_exit
|
||||||
|
|
||||||
|
class command_clear(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
self.honeypot.terminal.reset()
|
||||||
|
commands['/usr/bin/clear'] = command_clear
|
||||||
|
|
||||||
|
class command_vi(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
self.writeln('E558: Terminal entry not found in terminfo')
|
||||||
|
commands['/usr/bin/vi'] = command_vi
|
||||||
|
|
||||||
|
class command_hostname(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
self.writeln(self.honeypot.hostname)
|
||||||
|
commands['/bin/hostname'] = command_hostname
|
||||||
|
|
||||||
|
class command_uname(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
if len(self.args) and self.args[0].strip() == '-a':
|
||||||
|
self.writeln(
|
||||||
|
'Linux %s 2.6.26-2-686 #1 SMP Wed Nov 4 20:45:37 UTC 2009 i686 GNU/Linux' % \
|
||||||
|
self.honeypot.hostname)
|
||||||
|
else:
|
||||||
|
self.writeln('Linux')
|
||||||
|
commands['/bin/uname'] = command_uname
|
||||||
|
|
||||||
|
class command_ps(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
if len(self.args) and self.args[0].strip().count('a'):
|
||||||
|
output = (
|
||||||
|
'USER PID %%CPU %%MEM VSZ RSS TTY STAT START TIME COMMAND',
|
||||||
|
'root 1 0.0 0.1 2100 688 ? Ss Nov06 0:07 init [2] ',
|
||||||
|
'root 2 0.0 0.0 0 0 ? S< Nov06 0:00 [kthreadd]',
|
||||||
|
'root 3 0.0 0.0 0 0 ? S< Nov06 0:00 [migration/0]',
|
||||||
|
'root 4 0.0 0.0 0 0 ? S< Nov06 0:00 [ksoftirqd/0]',
|
||||||
|
'root 5 0.0 0.0 0 0 ? S< Nov06 0:00 [watchdog/0]',
|
||||||
|
'root 6 0.0 0.0 0 0 ? S< Nov06 0:17 [events/0]',
|
||||||
|
'root 7 0.0 0.0 0 0 ? S< Nov06 0:00 [khelper]',
|
||||||
|
'root 39 0.0 0.0 0 0 ? S< Nov06 0:00 [kblockd/0]',
|
||||||
|
'root 41 0.0 0.0 0 0 ? S< Nov06 0:00 [kacpid]',
|
||||||
|
'root 42 0.0 0.0 0 0 ? S< Nov06 0:00 [kacpi_notify]',
|
||||||
|
'root 170 0.0 0.0 0 0 ? S< Nov06 0:00 [kseriod]',
|
||||||
|
'root 207 0.0 0.0 0 0 ? S Nov06 0:01 [pdflush]',
|
||||||
|
'root 208 0.0 0.0 0 0 ? S Nov06 0:00 [pdflush]',
|
||||||
|
'root 209 0.0 0.0 0 0 ? S< Nov06 0:00 [kswapd0]',
|
||||||
|
'root 210 0.0 0.0 0 0 ? S< Nov06 0:00 [aio/0]',
|
||||||
|
'root 748 0.0 0.0 0 0 ? S< Nov06 0:00 [ata/0]',
|
||||||
|
'root 749 0.0 0.0 0 0 ? S< Nov06 0:00 [ata_aux]',
|
||||||
|
'root 929 0.0 0.0 0 0 ? S< Nov06 0:00 [scsi_eh_0]',
|
||||||
|
'root 1014 0.0 0.0 0 0 ? D< Nov06 0:03 [kjournald]',
|
||||||
|
'root 1087 0.0 0.1 2288 772 ? S<s Nov06 0:00 udevd --daemon',
|
||||||
|
'root 1553 0.0 0.0 0 0 ? S< Nov06 0:00 [kpsmoused]',
|
||||||
|
'root 2054 0.0 0.2 28428 1508 ? Sl Nov06 0:01 /usr/sbin/rsyslogd -c3',
|
||||||
|
'root 2103 0.0 0.2 2628 1196 tty1 Ss Nov06 0:00 /bin/login -- ',
|
||||||
|
'root 2105 0.0 0.0 1764 504 tty2 Ss+ Nov06 0:00 /sbin/getty 38400 tty2',
|
||||||
|
'root 2107 0.0 0.0 1764 504 tty3 Ss+ Nov06 0:00 /sbin/getty 38400 tty3',
|
||||||
|
'root 2109 0.0 0.0 1764 504 tty4 Ss+ Nov06 0:00 /sbin/getty 38400 tty4',
|
||||||
|
'root 2110 0.0 0.0 1764 504 tty5 Ss+ Nov06 0:00 /sbin/getty 38400 tty5',
|
||||||
|
'root 2112 0.0 0.0 1764 508 tty6 Ss+ Nov06 0:00 /sbin/getty 38400 tty6',
|
||||||
|
'root 2133 0.0 0.1 2180 620 ? S<s Nov06 0:00 dhclient3 -pf /var/run/dhclient.eth0.pid -lf /var/lib/dhcp3/dhclien',
|
||||||
|
'root 4969 0.0 0.1 5416 1024 ? Ss Nov08 0:00 /usr/sbin/sshd',
|
||||||
|
'root 5673 0.0 0.2 2924 1540 pts/0 Ss 04:30 0:00 -bash',
|
||||||
|
'root 5679 0.0 0.1 2432 928 pts/0 R+ 04:32 0:00 ps %s' % ' '.join(self.args),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
output = (
|
||||||
|
' PID TTY TIME CMD',
|
||||||
|
' 5673 pts/0 00:00:00 bash',
|
||||||
|
' 5677 pts/0 00:00:00 ps %s' % ' '.join(self.args),
|
||||||
|
)
|
||||||
|
for l in output:
|
||||||
|
self.writeln(l)
|
||||||
|
commands['/bin/ps'] = command_ps
|
||||||
|
|
||||||
|
class command_id(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
self.writeln('uid=0(root) gid=0(root) groups=0(root)')
|
||||||
|
commands['/usr/bin/id'] = command_id
|
||||||
|
|
||||||
|
class command_mount(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
if self.args and len(self.args[0].strip()):
|
||||||
|
return
|
||||||
|
for i in [
|
||||||
|
'/dev/sda1 on / type ext3 (rw,errors=remount-ro)',
|
||||||
|
'tmpfs on /lib/init/rw type tmpfs (rw,nosuid,mode=0755)',
|
||||||
|
'proc on /proc type proc (rw,noexec,nosuid,nodev)',
|
||||||
|
'sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)',
|
||||||
|
'udev on /dev type tmpfs (rw,mode=0755)',
|
||||||
|
'tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)',
|
||||||
|
'devpts on /dev/pts type devpts (rw,noexec,nosuid,gid=5,mode=620)',
|
||||||
|
]:
|
||||||
|
self.writeln(i)
|
||||||
|
commands['/usr/mount'] = command_mount
|
||||||
|
|
||||||
|
class command_pwd(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
self.writeln(self.honeypot.cwd)
|
||||||
|
commands['/bin/pwd'] = command_pwd
|
||||||
|
|
||||||
|
class command_passwd(HoneyPotCommand):
|
||||||
|
def start(self):
|
||||||
|
self.write('Enter new UNIX password: ')
|
||||||
|
self.honeypot.password_input = True
|
||||||
|
self.callbacks = [self.ask_again, self.finish]
|
||||||
|
|
||||||
|
def ask_again(self):
|
||||||
|
self.write('Retype new UNIX password: ')
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
self.honeypot.password_input = False
|
||||||
|
self.writeln('Sorry, passwords do not match')
|
||||||
|
self.writeln(
|
||||||
|
'passwd: Authentication information cannot be recovered')
|
||||||
|
self.writeln('passwd: password unchanged')
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def lineReceived(self, line):
|
||||||
|
print 'INPUT (passwd):', line
|
||||||
|
self.callbacks.pop(0)()
|
||||||
|
commands['/usr/bin/passwd'] = command_passwd
|
||||||
|
|
||||||
|
class command_reboot(HoneyPotCommand):
|
||||||
|
def start(self):
|
||||||
|
self.nextLine()
|
||||||
|
self.writeln(
|
||||||
|
'Broadcast message from root@%s (pts/0) (%s):' % \
|
||||||
|
(self.honeypot.hostname, time.ctime()))
|
||||||
|
self.nextLine()
|
||||||
|
self.writeln('The system is going down for reboot NOW!')
|
||||||
|
reactor.callLater(3, self.finish)
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
self.writeln('Connection to server closed.')
|
||||||
|
self.honeypot.hostname = 'localhost'
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
commands['/sbin/reboot'] = command_reboot
|
||||||
|
|
||||||
|
class command_nop(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
pass
|
||||||
|
commands['/bin/chmod'] = command_nop
|
||||||
|
commands['set'] = command_nop
|
||||||
|
commands['unset'] = command_nop
|
||||||
|
commands['history'] = command_nop
|
||||||
|
commands['export'] = command_nop
|
||||||
|
commands['/bin/bash'] = command_nop
|
||||||
|
commands['/bin/sh'] = command_nop
|
||||||
|
commands['/bin/kill'] = command_nop
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
45
kippo/commands/dice.py
Normal file
45
kippo/commands/dice.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
# Random commands when running new executables
|
||||||
|
|
||||||
|
from kippo.core.honeypot import HoneyPotCommand
|
||||||
|
|
||||||
|
commands = {}
|
||||||
|
clist = []
|
||||||
|
|
||||||
|
class command_orly(HoneyPotCommand):
|
||||||
|
def start(self):
|
||||||
|
self.orly()
|
||||||
|
|
||||||
|
def orly(self):
|
||||||
|
self.writeln(' ___ ')
|
||||||
|
self.writeln(' {o,o}')
|
||||||
|
self.writeln(' |)__)')
|
||||||
|
self.writeln(' -"-"-')
|
||||||
|
self.write('O RLY? ')
|
||||||
|
|
||||||
|
def lineReceived(self, data):
|
||||||
|
if data.strip().lower() in ('ya', 'yarly', 'ya rly', 'yes', 'y'):
|
||||||
|
self.writeln(' ___')
|
||||||
|
self.writeln(' {o,o}')
|
||||||
|
self.writeln(' (__(|')
|
||||||
|
self.writeln(' -"-"-')
|
||||||
|
self.writeln('NO WAI!')
|
||||||
|
self.exit()
|
||||||
|
return
|
||||||
|
self.orly()
|
||||||
|
clist.append(command_orly)
|
||||||
|
|
||||||
|
class command_wargames(HoneyPotCommand):
|
||||||
|
def start(self):
|
||||||
|
self.write('Shall we play a game? ')
|
||||||
|
|
||||||
|
def lineReceived(self, data):
|
||||||
|
self.writeln('A strange game. ' + \
|
||||||
|
'The only winning move is not to play. ' + \
|
||||||
|
'How about a nice game of chess?')
|
||||||
|
self.exit()
|
||||||
|
clist.append(command_wargames)
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
121
kippo/commands/ls.py
Normal file
121
kippo/commands/ls.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
from kippo.core.honeypot import HoneyPotCommand
|
||||||
|
from kippo.core.fs import *
|
||||||
|
import stat, time
|
||||||
|
|
||||||
|
commands = {}
|
||||||
|
|
||||||
|
class command_ls(HoneyPotCommand):
|
||||||
|
|
||||||
|
def uid2name(self, uid):
|
||||||
|
if uid == 0:
|
||||||
|
return 'root'
|
||||||
|
return uid
|
||||||
|
|
||||||
|
def gid2name(self, gid):
|
||||||
|
if gid == 0:
|
||||||
|
return 'root'
|
||||||
|
return gid
|
||||||
|
|
||||||
|
def call(self):
|
||||||
|
path = self.honeypot.cwd
|
||||||
|
paths = []
|
||||||
|
if len(self.args):
|
||||||
|
for arg in self.args:
|
||||||
|
if not arg.startswith('-'):
|
||||||
|
paths.append(self.honeypot.fs.resolve_path(arg,
|
||||||
|
self.honeypot.cwd))
|
||||||
|
|
||||||
|
self.show_hidden = False
|
||||||
|
func = self.do_ls_normal
|
||||||
|
for x in self.args:
|
||||||
|
if x.startswith('-') and x.count('l'):
|
||||||
|
func = self.do_ls_l
|
||||||
|
if x.startswith('-') and x.count('a'):
|
||||||
|
self.show_hidden = True
|
||||||
|
|
||||||
|
if not paths:
|
||||||
|
func(path)
|
||||||
|
else:
|
||||||
|
for path in paths:
|
||||||
|
func(path)
|
||||||
|
|
||||||
|
def do_ls_normal(self, path):
|
||||||
|
try:
|
||||||
|
files = self.honeypot.fs.list_files(path)
|
||||||
|
except:
|
||||||
|
self.honeypot.writeln(
|
||||||
|
'ls: cannot access %s: No such file or directory' % path)
|
||||||
|
return
|
||||||
|
if not len(files):
|
||||||
|
return
|
||||||
|
l = [x[A_NAME] for x in files \
|
||||||
|
if self.show_hidden or not x[A_NAME].startswith('.')]
|
||||||
|
if not l:
|
||||||
|
return
|
||||||
|
count = 0
|
||||||
|
maxlen = max([len(x) for x in l])
|
||||||
|
perline = int(self.honeypot.user.windowSize[1] / (maxlen + 1))
|
||||||
|
if self.show_hidden:
|
||||||
|
l.insert(0, '..')
|
||||||
|
l.insert(0, '.')
|
||||||
|
for f in l:
|
||||||
|
if count == perline:
|
||||||
|
count = 0
|
||||||
|
self.nextLine()
|
||||||
|
self.write(f.ljust(maxlen + 1))
|
||||||
|
count += 1
|
||||||
|
self.nextLine()
|
||||||
|
|
||||||
|
def do_ls_l(self, path):
|
||||||
|
try:
|
||||||
|
files = self.honeypot.fs.list_files(path)[:]
|
||||||
|
except:
|
||||||
|
self.honeypot.writeln(
|
||||||
|
'ls: cannot access %s: No such file or directory' % path)
|
||||||
|
return
|
||||||
|
|
||||||
|
largest = 0
|
||||||
|
if len(files):
|
||||||
|
largest = max([x[A_SIZE] for x in files])
|
||||||
|
|
||||||
|
# FIXME: should grab these off the parents instead
|
||||||
|
files.insert(0,
|
||||||
|
['..', T_DIR, 0, 0, 4096, 16877, time.time(), [], None])
|
||||||
|
files.insert(0,
|
||||||
|
['.', T_DIR, 0, 0, 4096, 16877, time.time(), [], None])
|
||||||
|
for file in files:
|
||||||
|
perms = ['-'] * 10
|
||||||
|
|
||||||
|
if file[A_MODE] & stat.S_IRUSR: perms[1] = 'r'
|
||||||
|
if file[A_MODE] & stat.S_IWUSR: perms[2] = 'w'
|
||||||
|
if file[A_MODE] & stat.S_IXUSR: perms[3] = 'x'
|
||||||
|
|
||||||
|
if file[A_MODE] & stat.S_IRGRP: perms[4] = 'r'
|
||||||
|
if file[A_MODE] & stat.S_IWGRP: perms[5] = 'w'
|
||||||
|
if file[A_MODE] & stat.S_IXGRP: perms[6] = 'x'
|
||||||
|
|
||||||
|
if file[A_MODE] & stat.S_IROTH: perms[7] = 'r'
|
||||||
|
if file[A_MODE] & stat.S_IWOTH: perms[8] = 'w'
|
||||||
|
if file[A_MODE] & stat.S_IXOTH: perms[9] = 'x'
|
||||||
|
|
||||||
|
if file[A_TYPE] == T_DIR:
|
||||||
|
perms[0] = 'd'
|
||||||
|
|
||||||
|
perms = ''.join(perms)
|
||||||
|
ctime = time.localtime(file[A_CTIME])
|
||||||
|
|
||||||
|
l = '%s 1 %s %s %s %s %s' % \
|
||||||
|
(perms,
|
||||||
|
self.uid2name(file[A_UID]),
|
||||||
|
self.gid2name(file[A_GID]),
|
||||||
|
str(file[A_SIZE]).rjust(len(str(largest))),
|
||||||
|
time.strftime('%Y-%m-%d %H:%M', ctime),
|
||||||
|
file[A_NAME])
|
||||||
|
|
||||||
|
self.honeypot.writeln(l)
|
||||||
|
commands['/bin/ls'] = command_ls
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
52
kippo/commands/ping.py
Normal file
52
kippo/commands/ping.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
from kippo.core.honeypot import HoneyPotCommand
|
||||||
|
from twisted.internet import reactor
|
||||||
|
import time, re, random, md5
|
||||||
|
|
||||||
|
commands = {}
|
||||||
|
|
||||||
|
class command_ping(HoneyPotCommand):
|
||||||
|
def start(self):
|
||||||
|
if not len(self.args):
|
||||||
|
for l in (
|
||||||
|
'Usage: ping [-LRUbdfnqrvVaA] [-c count] [-i interval] [-w deadline]',
|
||||||
|
' [-p pattern] [-s packetsize] [-t ttl] [-I interface or address]',
|
||||||
|
' [-M mtu discovery hint] [-S sndbuf]',
|
||||||
|
' [ -T timestamp option ] [ -Q tos ] [hop1 ...] destination',
|
||||||
|
):
|
||||||
|
self.writeln(l)
|
||||||
|
self.exit()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.host = self.args[0]
|
||||||
|
if re.match('^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$',
|
||||||
|
self.host):
|
||||||
|
self.ip = self.host
|
||||||
|
else:
|
||||||
|
s = md5.md5(self.host).hexdigest()
|
||||||
|
self.ip = '.'.join([str(int(x, 16)) for x in
|
||||||
|
(s[0:2], s[2:4], s[4:6], s[6:8])])
|
||||||
|
|
||||||
|
self.writeln('PING %s (%s) 56(84) bytes of data.' % \
|
||||||
|
(self.host, self.ip))
|
||||||
|
self.scheduled = reactor.callLater(0.2, self.showreply)
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
|
def showreply(self):
|
||||||
|
ms = 40 + random.random() * 10
|
||||||
|
self.writeln(
|
||||||
|
'64 bytes from %s (%s): icmp_seq=%d ttl=50 time=%.1f ms' % \
|
||||||
|
(self.host, self.ip, self.count + 1, ms))
|
||||||
|
self.count += 1
|
||||||
|
self.scheduled = reactor.callLater(1, self.showreply)
|
||||||
|
|
||||||
|
def ctrl_c(self):
|
||||||
|
self.scheduled.cancel()
|
||||||
|
self.writeln('--- %s ping statistics ---' % self.host)
|
||||||
|
self.writeln('%d packets transmitted, %d received, 0%% packet loss, time 907ms' % \
|
||||||
|
(self.count, self.count))
|
||||||
|
self.writeln('rtt min/avg/max/mdev = 48.264/50.352/52.441/2.100 ms')
|
||||||
|
self.exit()
|
||||||
|
commands['/bin/ping'] = command_ping
|
||||||
63
kippo/commands/ssh.py
Normal file
63
kippo/commands/ssh.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
from kippo.core.honeypot import HoneyPotCommand
|
||||||
|
from twisted.internet import reactor
|
||||||
|
import time
|
||||||
|
|
||||||
|
commands = {}
|
||||||
|
|
||||||
|
class command_ssh(HoneyPotCommand):
|
||||||
|
def start(self):
|
||||||
|
if not self.args:
|
||||||
|
for l in (
|
||||||
|
'usage: ssh [-1246AaCfgKkMNnqsTtVvXxY] [-b bind_address] [-c cipher_spec]',
|
||||||
|
' [-D [bind_address:]port] [-e escape_char] [-F configfile]',
|
||||||
|
' [-i identity_file] [-L [bind_address:]port:host:hostport]',
|
||||||
|
' [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]',
|
||||||
|
' [-R [bind_address:]port:host:hostport] [-S ctl_path]',
|
||||||
|
' [-w local_tun[:remote_tun]] [user@]hostname [command]',
|
||||||
|
):
|
||||||
|
self.writeln(l)
|
||||||
|
self.exit()
|
||||||
|
return
|
||||||
|
self.host = self.args[0].strip()
|
||||||
|
self.writeln('The authenticity of host \'187.42.2.9 (187.42.2.9)\' can\'t be established.')
|
||||||
|
self.writeln('RSA key fingerprint is 9d:30:97:8a:9e:48:0d:de:04:8d:76:3a:7b:4b:30:f8.')
|
||||||
|
self.write('Are you sure you want to continue connecting (yes/no)? ')
|
||||||
|
self.callbacks = [self.yesno, self.wait]
|
||||||
|
|
||||||
|
def yesno(self, line):
|
||||||
|
host = line.strip()
|
||||||
|
self.writeln(
|
||||||
|
'Warning: Permanently added \'%s\' (RSA) to the list of known hosts.' % \
|
||||||
|
host)
|
||||||
|
self.write('%s\'s password: ' % self.host)
|
||||||
|
self.honeypot.password_input = True
|
||||||
|
|
||||||
|
def wait(self, line):
|
||||||
|
reactor.callLater(2, self.finish, line)
|
||||||
|
|
||||||
|
def finish(self, line):
|
||||||
|
self.pause = False
|
||||||
|
user, rest, host = 'root', self.host, 'localhost'
|
||||||
|
if self.host.count('@'):
|
||||||
|
user, rest = self.host.split('@', 1)
|
||||||
|
rest = rest.strip().split('.')
|
||||||
|
if len(rest) and rest[0].isalpha():
|
||||||
|
host = rest[0]
|
||||||
|
self.honeypot.hostname = host
|
||||||
|
self.honeypot.password_input = False
|
||||||
|
self.writeln(
|
||||||
|
'Linux %s 2.6.26-2-686 #1 SMP Wed Nov 4 20:45:37 UTC 2009 i686' % \
|
||||||
|
self.honeypot.hostname)
|
||||||
|
self.writeln('Last login: %s from 192.168.9.4' % \
|
||||||
|
time.ctime(time.time() - 123123))
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def lineReceived(self, line):
|
||||||
|
if len(self.callbacks):
|
||||||
|
self.callbacks.pop(0)(line)
|
||||||
|
commands['/usr/bin/ssh'] = command_ssh
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
66
kippo/commands/tar.py
Normal file
66
kippo/commands/tar.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
from kippo.core.honeypot import HoneyPotCommand
|
||||||
|
from kippo.core.fs import *
|
||||||
|
from kippo.commands import dice
|
||||||
|
import time, random, tarfile, os
|
||||||
|
|
||||||
|
commands = {}
|
||||||
|
|
||||||
|
class command_tar(HoneyPotCommand):
|
||||||
|
def call(self):
|
||||||
|
if len(self.args) < 2:
|
||||||
|
self.writeln('tar: You must specify one of the `-Acdtrux\' options')
|
||||||
|
self.writeln('Try `tar --help\' or `tar --usage\' for more information.')
|
||||||
|
return
|
||||||
|
|
||||||
|
filename = self.args[1]
|
||||||
|
|
||||||
|
extract = False
|
||||||
|
if 'x' in self.args[0]:
|
||||||
|
extract = True
|
||||||
|
verbose = False
|
||||||
|
if 'v' in self.args[0]:
|
||||||
|
verbose = True
|
||||||
|
|
||||||
|
path = self.fs.resolve_path(filename, self.honeypot.cwd)
|
||||||
|
if not path or not self.honeypot.fs.exists(path):
|
||||||
|
self.writeln('tar: %s: Cannot open: No such file or directory' % \
|
||||||
|
filename)
|
||||||
|
self.writeln('tar: Error is not recoverable: exiting now')
|
||||||
|
self.writeln('tar: Child returned status 2')
|
||||||
|
self.writeln('tar: Error exit delayed from previous errors')
|
||||||
|
return
|
||||||
|
|
||||||
|
f = self.fs.getfile(path)
|
||||||
|
if not f[A_REALFILE]:
|
||||||
|
self.writeln('tar: this does not look like a tar archive')
|
||||||
|
self.writeln('tar: skipping to next header')
|
||||||
|
self.writeln('tar: error exit delayed from previous errors')
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
t = tarfile.open(f[A_REALFILE])
|
||||||
|
except:
|
||||||
|
self.writeln('tar: this does not look like a tar archive')
|
||||||
|
self.writeln('tar: skipping to next header')
|
||||||
|
self.writeln('tar: error exit delayed from previous errors')
|
||||||
|
return
|
||||||
|
|
||||||
|
for f in t:
|
||||||
|
dest = self.fs.resolve_path(f.name.strip('/'), self.honeypot.cwd)
|
||||||
|
if verbose:
|
||||||
|
self.writeln(f.name)
|
||||||
|
if not extract or not len(dest):
|
||||||
|
continue
|
||||||
|
if f.isdir():
|
||||||
|
self.fs.mkdir(dest, 0, 0, 4096, f.mode, f.mtime)
|
||||||
|
elif f.isfile():
|
||||||
|
self.fs.mkfile(dest, 0, 0, f.size, f.mode, f.mtime)
|
||||||
|
self.honeypot.commands[dest] = random.choice(dice.clist)
|
||||||
|
else:
|
||||||
|
print 'tar: skipping [%s]' % f.name
|
||||||
|
commands['/bin/tar'] = command_tar
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
194
kippo/commands/wget.py
Normal file
194
kippo/commands/wget.py
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
from kippo.core.honeypot import HoneyPotCommand
|
||||||
|
from kippo.core.fs import *
|
||||||
|
from twisted.web import client
|
||||||
|
from twisted.internet import reactor
|
||||||
|
import stat, time, urlparse, random, re
|
||||||
|
|
||||||
|
commands = {}
|
||||||
|
|
||||||
|
def tdiff(seconds):
|
||||||
|
t = seconds
|
||||||
|
days = int(t / (24 * 60 * 60))
|
||||||
|
t -= (days * 24 * 60 * 60)
|
||||||
|
hours = int(t / (60 * 60))
|
||||||
|
t -= (hours * 60 * 60)
|
||||||
|
minutes = int(t / 60)
|
||||||
|
t -= (minutes * 60)
|
||||||
|
|
||||||
|
s = '%ds' % int(t)
|
||||||
|
if minutes >= 1: s = '%dm %s' % (minutes, s)
|
||||||
|
if hours >= 1: s = '%dh %s' % (hours, s)
|
||||||
|
if days >= 1: s = '%dd %s' % (days, s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def sizeof_fmt(num):
|
||||||
|
for x in ['bytes','K','M','G','T']:
|
||||||
|
if num < 1024.0:
|
||||||
|
return "%d%s" % (num, x)
|
||||||
|
num /= 1024.0
|
||||||
|
|
||||||
|
# Luciano Ramalho @ http://code.activestate.com/recipes/498181/
|
||||||
|
def splitthousands( s, sep=','):
|
||||||
|
if len(s) <= 3: return s
|
||||||
|
return splitthousands(s[:-3], sep) + sep + s[-3:]
|
||||||
|
|
||||||
|
class command_wget(HoneyPotCommand):
|
||||||
|
def start(self):
|
||||||
|
url = None
|
||||||
|
for arg in self.args:
|
||||||
|
if arg.startswith('-'):
|
||||||
|
continue
|
||||||
|
url = arg.strip()
|
||||||
|
break
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
self.writeln('wget: missing URL')
|
||||||
|
self.writeln('Usage: wget [OPTION]... [URL]...')
|
||||||
|
self.nextLine()
|
||||||
|
self.writeln('Try `wget --help\' for more options.')
|
||||||
|
self.exit()
|
||||||
|
return
|
||||||
|
|
||||||
|
if url and not url.startswith('http://'):
|
||||||
|
url = 'http://%s' % url
|
||||||
|
|
||||||
|
urldata = urlparse.urlparse(url)
|
||||||
|
|
||||||
|
outfile = urldata.path.split('/')[-1]
|
||||||
|
if not len(outfile.strip()) or not urldata.path.count('/'):
|
||||||
|
outfile = 'index.html'
|
||||||
|
|
||||||
|
self.safeoutfile = '%s/%s_%s' % \
|
||||||
|
(self.honeypot.env.cfg.get('honeypot', 'download_path'),
|
||||||
|
time.strftime('%Y%m%d%H%M%S'),
|
||||||
|
re.sub('[^A-Za-z0-9]', '_', url))
|
||||||
|
self.deferred = self.download(url, outfile,
|
||||||
|
file(self.safeoutfile, 'wb'))
|
||||||
|
if self.deferred:
|
||||||
|
self.deferred.addCallback(self.success)
|
||||||
|
self.deferred.addErrback(self.error, url)
|
||||||
|
|
||||||
|
def download(self, url, fakeoutfile, outputfile, *args, **kwargs):
|
||||||
|
scheme, host, port, path = client._parse(url)
|
||||||
|
if scheme == 'https':
|
||||||
|
self.writeln('Sorry, SSL not supported in this release')
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.writeln('--%s-- %s' % (time.strftime('%Y-%m-%d %H:%M:%S'), url))
|
||||||
|
self.writeln('Connecting to %s:%d... connected.' % (host, port))
|
||||||
|
self.write('HTTP request sent, awaiting response... ')
|
||||||
|
|
||||||
|
factory = HTTPProgressDownloader(
|
||||||
|
self, fakeoutfile, url, outputfile, *args, **kwargs)
|
||||||
|
self.connection = reactor.connectTCP(host, port, factory)
|
||||||
|
return factory.deferred
|
||||||
|
|
||||||
|
def ctrl_c(self):
|
||||||
|
self.writeln('^C')
|
||||||
|
self.connection.transport.loseConnection()
|
||||||
|
|
||||||
|
def success(self, data):
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def error(self, error, url):
|
||||||
|
if hasattr(error, 'getErrorMessage'): # exceptions
|
||||||
|
error = error.getErrorMessage()
|
||||||
|
self.writeln(error)
|
||||||
|
# Real wget also adds this:
|
||||||
|
#self.writeln('%s ERROR 404: Not Found.' % \
|
||||||
|
# time.strftime('%Y-%m-%d %T'))
|
||||||
|
self.exit()
|
||||||
|
commands['/usr/bin/wget'] = command_wget
|
||||||
|
|
||||||
|
# from http://code.activestate.com/recipes/525493/
|
||||||
|
class HTTPProgressDownloader(client.HTTPDownloader):
|
||||||
|
def __init__(self, wget, fakeoutfile, url, outfile, headers=None):
|
||||||
|
client.HTTPDownloader.__init__(self, url, outfile, headers=headers)
|
||||||
|
self.status = None
|
||||||
|
self.wget = wget
|
||||||
|
self.fakeoutfile = fakeoutfile
|
||||||
|
self.lastupdate = 0
|
||||||
|
self.started = time.time()
|
||||||
|
self.proglen = 0
|
||||||
|
|
||||||
|
def noPage(self, reason): # called for non-200 responses
|
||||||
|
if self.status == '304':
|
||||||
|
client.HTTPDownloader.page(self, '')
|
||||||
|
else:
|
||||||
|
client.HTTPDownloader.noPage(self, reason)
|
||||||
|
|
||||||
|
def gotHeaders(self, headers):
|
||||||
|
if self.status == '200':
|
||||||
|
self.wget.writeln('200 OK')
|
||||||
|
if headers.has_key('content-length'):
|
||||||
|
self.totallength = int(headers['content-length'][0])
|
||||||
|
else:
|
||||||
|
self.totallength = 0
|
||||||
|
if headers.has_key('content-type'):
|
||||||
|
self.contenttype = headers['content-type'][0]
|
||||||
|
else:
|
||||||
|
self.contenttype = 'text/whatever'
|
||||||
|
self.currentlength = 0.0
|
||||||
|
|
||||||
|
if self.totallength > 0:
|
||||||
|
self.wget.writeln('Length: %d (%s) [%s]' % \
|
||||||
|
(self.totallength,
|
||||||
|
sizeof_fmt(self.totallength),
|
||||||
|
self.contenttype))
|
||||||
|
else:
|
||||||
|
self.wget.writeln('Length: unspecified [%s]' % \
|
||||||
|
(self.contenttype))
|
||||||
|
self.wget.writeln('Saving to: `%s' % self.fakeoutfile)
|
||||||
|
self.wget.honeypot.terminal.nextLine()
|
||||||
|
|
||||||
|
return client.HTTPDownloader.gotHeaders(self, headers)
|
||||||
|
|
||||||
|
def pagePart(self, data):
|
||||||
|
if self.status == '200':
|
||||||
|
self.currentlength += len(data)
|
||||||
|
if (time.time() - self.lastupdate) < 0.5:
|
||||||
|
return client.HTTPDownloader.pagePart(self, data)
|
||||||
|
if self.totallength:
|
||||||
|
percent = (self.currentlength/self.totallength)*100
|
||||||
|
spercent = "%i%%" % percent
|
||||||
|
else:
|
||||||
|
spercent = '%dK' % (self.currentlength/1000)
|
||||||
|
percent = 0
|
||||||
|
self.speed = self.currentlength / (time.time() - self.started)
|
||||||
|
eta = (self.totallength - self.currentlength) / self.speed
|
||||||
|
s = '\r%s [%s] %s %dK/s eta %s' % \
|
||||||
|
(spercent.rjust(3),
|
||||||
|
('%s>' % (int(39.0 / 100.0 * percent) * '=')).ljust(39),
|
||||||
|
splitthousands(str(int(self.currentlength))).ljust(12),
|
||||||
|
self.speed / 1000,
|
||||||
|
tdiff(eta))
|
||||||
|
self.wget.write(s.ljust(self.proglen))
|
||||||
|
self.proglen = len(s)
|
||||||
|
self.lastupdate = time.time()
|
||||||
|
return client.HTTPDownloader.pagePart(self, data)
|
||||||
|
|
||||||
|
def pageEnd(self):
|
||||||
|
if self.totallength != 0 and self.currentlength != self.totallength:
|
||||||
|
return client.HTTPDownloader.pageEnd(self)
|
||||||
|
self.wget.write('\r100%%[%s] %s %dK/s' % \
|
||||||
|
('%s>' % (38 * '='),
|
||||||
|
splitthousands(str(int(self.totallength))).ljust(12),
|
||||||
|
self.speed / 1000))
|
||||||
|
self.wget.honeypot.terminal.nextLine()
|
||||||
|
self.wget.honeypot.terminal.nextLine()
|
||||||
|
self.wget.writeln(
|
||||||
|
'%s (%d KB/s) - `%s\' saved [%d/%d]' % \
|
||||||
|
(time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
self.speed / 1000,
|
||||||
|
self.fakeoutfile, self.currentlength, self.totallength))
|
||||||
|
outfile = '%s/%s' % (self.wget.honeypot.cwd, self.fakeoutfile)
|
||||||
|
self.wget.fs.mkfile(outfile, 0, 0, self.totallength, 33188)
|
||||||
|
self.wget.fs.update_realfile(
|
||||||
|
self.wget.fs.getfile(outfile),
|
||||||
|
self.wget.safeoutfile)
|
||||||
|
return client.HTTPDownloader.pageEnd(self)
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
0
kippo/core/__init__.py
Normal file
0
kippo/core/__init__.py
Normal file
14
kippo/core/config.py
Normal file
14
kippo/core/config.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
import ConfigParser, os
|
||||||
|
|
||||||
|
def config():
|
||||||
|
cfg = ConfigParser.ConfigParser()
|
||||||
|
for f in ('kippo.cfg', '/etc/kippo/kippo.cfg', '/etc/kippo.cfg'):
|
||||||
|
if os.path.exists(f):
|
||||||
|
cfg.read('kippo.cfg')
|
||||||
|
return cfg
|
||||||
|
return None
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
126
kippo/core/fs.py
Normal file
126
kippo/core/fs.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
import os, time
|
||||||
|
|
||||||
|
A_NAME, \
|
||||||
|
A_TYPE, \
|
||||||
|
A_UID, \
|
||||||
|
A_GID, \
|
||||||
|
A_SIZE, \
|
||||||
|
A_MODE, \
|
||||||
|
A_CTIME, \
|
||||||
|
A_CONTENTS, \
|
||||||
|
A_TARGET, \
|
||||||
|
A_REALFILE = range(0, 10)
|
||||||
|
T_LINK, \
|
||||||
|
T_DIR, \
|
||||||
|
T_FILE, \
|
||||||
|
T_BLK, \
|
||||||
|
T_CHR, \
|
||||||
|
T_SOCK, \
|
||||||
|
T_FIFO = range(0, 7)
|
||||||
|
|
||||||
|
class HoneyPotFilesystem(object):
|
||||||
|
def __init__(self, fs):
|
||||||
|
self.fs = fs
|
||||||
|
|
||||||
|
def resolve_path(self, path, cwd):
|
||||||
|
pieces = path.rstrip('/').split('/')
|
||||||
|
|
||||||
|
if path[0] == '/':
|
||||||
|
cwd = []
|
||||||
|
else:
|
||||||
|
cwd = [x for x in cwd.split('/') if len(x) and x is not None]
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
if not len(pieces):
|
||||||
|
break
|
||||||
|
piece = pieces.pop(0)
|
||||||
|
if piece == '..':
|
||||||
|
if len(cwd): cwd.pop()
|
||||||
|
continue
|
||||||
|
if piece in ('.', ''):
|
||||||
|
continue
|
||||||
|
cwd.append(piece)
|
||||||
|
|
||||||
|
return '/%s' % '/'.join(cwd)
|
||||||
|
|
||||||
|
def get_path(self, path):
|
||||||
|
p = self.fs
|
||||||
|
for i in path.split('/'):
|
||||||
|
if not i:
|
||||||
|
continue
|
||||||
|
p = [x for x in p[A_CONTENTS] if x[A_NAME] == i][0]
|
||||||
|
return p[A_CONTENTS]
|
||||||
|
|
||||||
|
def list_files(self, path):
|
||||||
|
return self.get_path(path)
|
||||||
|
|
||||||
|
def exists(self, path):
|
||||||
|
f = self.getfile(path)
|
||||||
|
if f is not False:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update_realfile(self, f, realfile):
|
||||||
|
if not f[A_REALFILE] and os.path.exists(realfile) and \
|
||||||
|
not os.path.islink(realfile) and os.path.isfile(realfile) and \
|
||||||
|
f[A_SIZE] < 25000000:
|
||||||
|
print 'Updating realfile to %s' % realfile
|
||||||
|
f[A_REALFILE] = realfile
|
||||||
|
|
||||||
|
def realfile(self, f, path):
|
||||||
|
self.update_realfile(f, path)
|
||||||
|
if f[A_REALFILE]:
|
||||||
|
return f[A_REALFILE]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getfile(self, path):
|
||||||
|
pieces = path.strip('/').split('/')
|
||||||
|
p = self.fs
|
||||||
|
while 1:
|
||||||
|
if not len(pieces):
|
||||||
|
break
|
||||||
|
piece = pieces.pop(0)
|
||||||
|
if piece not in [x[A_NAME] for x in p[A_CONTENTS]]:
|
||||||
|
return False
|
||||||
|
p = [x for x in p[A_CONTENTS] \
|
||||||
|
if x[A_NAME] == piece][0]
|
||||||
|
return p
|
||||||
|
|
||||||
|
def mkfile(self, path, uid, gid, size, mode, ctime = None):
|
||||||
|
if ctime is None:
|
||||||
|
ctime = time.time()
|
||||||
|
dir = self.get_path(os.path.dirname(path))
|
||||||
|
outfile = os.path.basename(path)
|
||||||
|
if outfile in [x[A_NAME] for x in dir]:
|
||||||
|
dir.remove([x for x in dir if x[A_NAME] == outfile][0])
|
||||||
|
dir.append([outfile, T_FILE, uid, gid, size, mode, ctime, [],
|
||||||
|
None, None])
|
||||||
|
return True
|
||||||
|
|
||||||
|
def mkdir(self, path, uid, gid, size, mode, ctime = None):
|
||||||
|
if ctime is None:
|
||||||
|
ctime = time.time()
|
||||||
|
if not len(path.strip('/')):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
dir = self.get_path(os.path.dirname(path.strip('/')))
|
||||||
|
except IndexError:
|
||||||
|
return False
|
||||||
|
dir.append([os.path.basename(path), T_DIR, uid, gid, size, mode,
|
||||||
|
ctime, [], None, None])
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_dir(self, path):
|
||||||
|
if path == '/':
|
||||||
|
return True
|
||||||
|
dir = self.get_path(os.path.dirname(path))
|
||||||
|
l = [x for x in dir
|
||||||
|
if x[A_NAME] == os.path.basename(path) and
|
||||||
|
x[A_TYPE] == T_DIR]
|
||||||
|
if l:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
303
kippo/core/honeypot.py
Normal file
303
kippo/core/honeypot.py
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
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.protocols.policies import TrafficLoggingFactory
|
||||||
|
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
|
||||||
|
|
||||||
|
from kippo.core import ttylog, fs
|
||||||
|
from kippo.core.config import config
|
||||||
|
import commands
|
||||||
|
|
||||||
|
class HoneyPotCommand(object):
|
||||||
|
def __init__(self, honeypot, *args):
|
||||||
|
self.honeypot = honeypot
|
||||||
|
self.args = args
|
||||||
|
self.writeln = self.honeypot.writeln
|
||||||
|
self.write = self.honeypot.terminal.write
|
||||||
|
self.nextLine = self.honeypot.terminal.nextLine
|
||||||
|
self.fs = self.honeypot.fs
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.call()
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def call(self):
|
||||||
|
self.honeypot.writeln('Hello World! [%s]' % repr(self.args))
|
||||||
|
|
||||||
|
def exit(self):
|
||||||
|
self.honeypot.cmdstack.pop()
|
||||||
|
self.honeypot.cmdstack[-1].resume()
|
||||||
|
|
||||||
|
def ctrl_c(self):
|
||||||
|
print 'Received CTRL-C, exiting..'
|
||||||
|
self.writeln('^C')
|
||||||
|
self.exit()
|
||||||
|
|
||||||
|
def lineReceived(self, line):
|
||||||
|
print 'INPUT: %s' % line
|
||||||
|
|
||||||
|
def resume(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class HoneyPotShell(object):
|
||||||
|
def __init__(self, honeypot):
|
||||||
|
self.honeypot = honeypot
|
||||||
|
self.showPrompt()
|
||||||
|
|
||||||
|
def lineReceived(self, line):
|
||||||
|
print 'CMD: %s' % line
|
||||||
|
if not len(line.strip()):
|
||||||
|
self.showPrompt()
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
cmdAndArgs = shlex.split(line.strip())
|
||||||
|
except:
|
||||||
|
self.honeypot.writeln(
|
||||||
|
'-bash: syntax error: unexpected end of file')
|
||||||
|
self.showPrompt()
|
||||||
|
return
|
||||||
|
cmd, args = cmdAndArgs[0], []
|
||||||
|
if len(cmdAndArgs) > 1:
|
||||||
|
args = cmdAndArgs[1:]
|
||||||
|
cmdclass = self.honeypot.getCommand(cmd)
|
||||||
|
if cmdclass:
|
||||||
|
obj = cmdclass(self.honeypot, *args)
|
||||||
|
self.honeypot.cmdstack.append(obj)
|
||||||
|
self.honeypot.setTypeoverMode()
|
||||||
|
obj.start()
|
||||||
|
else:
|
||||||
|
if len(line.strip()):
|
||||||
|
self.honeypot.writeln('bash: %s: command not found' % cmd)
|
||||||
|
self.showPrompt()
|
||||||
|
|
||||||
|
def resume(self):
|
||||||
|
self.honeypot.setInsertMode()
|
||||||
|
self.showPrompt()
|
||||||
|
|
||||||
|
def showPrompt(self):
|
||||||
|
prompt = '%s:%%(path)s# ' % self.honeypot.hostname
|
||||||
|
path = self.honeypot.cwd
|
||||||
|
if path == '/root':
|
||||||
|
path = '~'
|
||||||
|
attrs = {'path': path}
|
||||||
|
self.honeypot.terminal.write(prompt % attrs)
|
||||||
|
|
||||||
|
def ctrl_c(self):
|
||||||
|
self.honeypot.terminal.nextLine()
|
||||||
|
self.showPrompt()
|
||||||
|
|
||||||
|
class HoneyPotProtocol(recvline.HistoricRecvLine):
|
||||||
|
def __init__(self, user, env):
|
||||||
|
self.user = user
|
||||||
|
self.env = env
|
||||||
|
self.cwd = '/root'
|
||||||
|
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
|
||||||
|
self.commands = copy(self.env.commands)
|
||||||
|
self.password_input = False
|
||||||
|
self.cmdstack = []
|
||||||
|
|
||||||
|
def connectionMade(self):
|
||||||
|
recvline.HistoricRecvLine.connectionMade(self)
|
||||||
|
self.cmdstack = [HoneyPotShell(self)]
|
||||||
|
|
||||||
|
def connectionLost(self, reason):
|
||||||
|
recvline.HistoricRecvLine.connectionLost(self, reason)
|
||||||
|
# not sure why i need to do this:
|
||||||
|
del self.fs
|
||||||
|
del self.commands
|
||||||
|
|
||||||
|
# Overriding to prevent terminal.reset()
|
||||||
|
def initializeScreen(self):
|
||||||
|
self.setInsertMode()
|
||||||
|
|
||||||
|
def getCommand(self, cmd):
|
||||||
|
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' % (x, cmd) for x in \
|
||||||
|
'/bin', '/usr/bin', '/sbin', '/usr/sbin']:
|
||||||
|
if self.fs.exists(i):
|
||||||
|
path = i
|
||||||
|
break
|
||||||
|
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 keystrokeReceived(self, keyID, modifier):
|
||||||
|
if type(keyID) == type(''):
|
||||||
|
ttylog.ttylog_write(self.terminal.ttylog_file, len(keyID),
|
||||||
|
ttylog.DIR_READ, time.time(), keyID)
|
||||||
|
if keyID == '\x03':
|
||||||
|
self.cmdstack[-1].ctrl_c()
|
||||||
|
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 writeln(self, data):
|
||||||
|
self.terminal.write(data)
|
||||||
|
self.terminal.nextLine()
|
||||||
|
|
||||||
|
class LoggingServerProtocol(insults.ServerProtocol):
|
||||||
|
def connectionMade(self):
|
||||||
|
self.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' % self.ttylog_file
|
||||||
|
ttylog.ttylog_open(self.ttylog_file, time.time())
|
||||||
|
self.ttylog_open = True
|
||||||
|
insults.ServerProtocol.connectionMade(self)
|
||||||
|
|
||||||
|
def write(self, bytes):
|
||||||
|
if self.ttylog_open:
|
||||||
|
ttylog.ttylog_write(self.ttylog_file, len(bytes),
|
||||||
|
ttylog.DIR_WRITE, time.time(), bytes)
|
||||||
|
insults.ServerProtocol.write(self, bytes)
|
||||||
|
|
||||||
|
def connectionLost(self, reason):
|
||||||
|
if self.ttylog_open:
|
||||||
|
ttylog.ttylog_close(self.ttylog_file, time.time())
|
||||||
|
self.ttylog_open = False
|
||||||
|
insults.ServerProtocol.connectionLost(self, reason)
|
||||||
|
|
||||||
|
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':session.SSHSession})
|
||||||
|
|
||||||
|
def openShell(self, protocol):
|
||||||
|
serverProtocol = LoggingServerProtocol(HoneyPotProtocol, self, self.env)
|
||||||
|
serverProtocol.makeConnection(protocol)
|
||||||
|
protocol.makeConnection(session.wrapProtocol(serverProtocol))
|
||||||
|
|
||||||
|
def getPty(self, terminal, windowSize, attrs):
|
||||||
|
self.windowSize = windowSize
|
||||||
|
return None
|
||||||
|
|
||||||
|
def execCommand(self, protocol, cmd):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def closed(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def windowChanged(self, windowSize):
|
||||||
|
self.windowSize = windowSize
|
||||||
|
|
||||||
|
class HoneyPotEnvironment(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.cfg = config()
|
||||||
|
self.commands = {}
|
||||||
|
import kippo.commands
|
||||||
|
for c in kippo.commands.__all__:
|
||||||
|
module = __import__('kippo.commands.%s' % c,
|
||||||
|
globals(), locals(), ['commands'])
|
||||||
|
self.commands.update(module.commands)
|
||||||
|
self.fs = pickle.load(file(
|
||||||
|
self.cfg.get('honeypot', 'filesystem_file')))
|
||||||
|
|
||||||
|
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."
|
||||||
|
|
||||||
|
# As implemented by Kojoney
|
||||||
|
class HoneyPotSSHFactory(factory.SSHFactory):
|
||||||
|
#publicKeys = {'ssh-rsa': keys.getPublicKeyString(data=publicKey)}
|
||||||
|
#privateKeys = {'ssh-rsa': keys.getPrivateKeyObject(data=privateKey)}
|
||||||
|
services = {
|
||||||
|
'ssh-userauth': userauth.SSHUserAuthServer,
|
||||||
|
'ssh-connection': connection.SSHConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
def buildProtocol(self, addr):
|
||||||
|
# FIXME: try to mimic something real 100%
|
||||||
|
t = transport.SSHServerTransport()
|
||||||
|
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,)
|
||||||
|
|
||||||
|
def __init__(self, users):
|
||||||
|
self.users = users
|
||||||
|
|
||||||
|
def requestAvatarId(self, credentials):
|
||||||
|
if (credentials.username, credentials.password) in self.users:
|
||||||
|
print 'login attempt [%s/%s] succeeded' % \
|
||||||
|
(credentials.username, credentials.password)
|
||||||
|
return defer.succeed(credentials.username)
|
||||||
|
else:
|
||||||
|
print 'login attempt [%s/%s] failed' % \
|
||||||
|
(credentials.username, credentials.password)
|
||||||
|
return defer.fail(error.UnauthorizedLogin())
|
||||||
|
|
||||||
|
def getRSAKeys():
|
||||||
|
if not (os.path.exists('public.key') and os.path.exists('private.key')):
|
||||||
|
# generate a RSA keypair
|
||||||
|
print "Generating RSA keypair..."
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
KEY_LENGTH = 1024
|
||||||
|
rsaKey = RSA.generate(KEY_LENGTH, common.entropy.get_bytes)
|
||||||
|
publicKeyString = keys.makePublicKeyString(rsaKey)
|
||||||
|
privateKeyString = keys.makePrivateKeyString(rsaKey)
|
||||||
|
# save keys for next time
|
||||||
|
file('public.key', 'w+b').write(publicKeyString)
|
||||||
|
file('private.key', 'w+b').write(privateKeyString)
|
||||||
|
print "done."
|
||||||
|
else:
|
||||||
|
publicKeyString = file('public.key').read()
|
||||||
|
privateKeyString = file('private.key').read()
|
||||||
|
return publicKeyString, privateKeyString
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
30
kippo/core/ttylog.py
Normal file
30
kippo/core/ttylog.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (c) 2009 Upi Tamminen <desaster@gmail.com>
|
||||||
|
# See the COPYRIGHT file for more information
|
||||||
|
|
||||||
|
# Should be compatible with user mode linux
|
||||||
|
|
||||||
|
import struct, sys
|
||||||
|
|
||||||
|
OP_OPEN, OP_CLOSE, OP_WRITE, OP_EXEC = 1, 2, 3, 4
|
||||||
|
DIR_READ, DIR_WRITE = 1, 2
|
||||||
|
|
||||||
|
def ttylog_write(logfile, len, direction, stamp, data = None):
|
||||||
|
f = file(logfile, 'a')
|
||||||
|
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
|
||||||
|
f.write(struct.pack('iLiiLL', 3, 0, len, direction, sec, usec))
|
||||||
|
f.write(data)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def ttylog_open(logfile, stamp):
|
||||||
|
f = file(logfile, 'a')
|
||||||
|
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
|
||||||
|
f.write(struct.pack('iLiiLL', 1, 0, 0, 0, sec, usec))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def ttylog_close(logfile, stamp):
|
||||||
|
f = file(logfile, 'a')
|
||||||
|
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
|
||||||
|
f.write(struct.pack('iLiiLL', 2, 0, 0, 0, sec, usec))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# vim: set sw=4 et:
|
||||||
Reference in New Issue
Block a user