This commit is contained in:
Michel Oosterhof
2015-12-29 12:23:08 +00:00
31 changed files with 522 additions and 487 deletions

View File

@@ -1,4 +1,6 @@
* 2015-12-28 Interact port (default 5123) only listens on loopback interface now (127.0.0.1)
* 2015-12-24 Redirect to file (>) now works for most commands and is logged in dl/ directory
* 2015-12-06 UID information is now retrieved from honeyfs/etc/passwd. If you added additional users
you will need to add these to the passwd file as well
* 2015-12-04 New 'free' command with '-h' and '-m' options

View File

@@ -7,7 +7,6 @@ __all__ = [
'base',
'busybox',
'curl',
'dice',
'env',
'ethtool',
'free',
@@ -17,7 +16,6 @@ __all__ = [
'iptables',
'last',
'ls',
'malware',
'netstat',
'nohup',
'ping',

View File

@@ -9,22 +9,9 @@ from twisted.python import log
from cowrie.core.honeypot import HoneyPotCommand
from cowrie.core.fs import *
from cowrie.commands import dice, malware
commands = {}
def pick_handler(cmd, size):
"""
"""
if size in malware.slist:
handler = malware.slist[size]
elif cmd in malware.clist:
handler = malware.clist[cmd]
else:
handler = random.choice(dice.clist)
return handler
class command_tar(HoneyPotCommand):
"""
@@ -92,8 +79,6 @@ class command_tar(HoneyPotCommand):
elif f.isfile():
self.mkfullpath(os.path.dirname(dest), f)
self.fs.mkfile(dest, 0, 0, f.size, f.mode, f.mtime)
self.protocol.commands[dest] = \
pick_handler(os.path.basename(dest), f.size)
else:
log.msg( 'tar: skipping [%s]' % f.name )

View File

@@ -25,4 +25,4 @@ class command_which(HoneyPotCommand):
continue
# Definition
commands['/bin/which'] = command_which
commands['which'] = command_which

View File

@@ -219,4 +219,3 @@ class AuthRandom(object):
self.savevars()
return auth
# vim: set sw=4 et:

63
cowrie/core/avatar.py Normal file
View File

@@ -0,0 +1,63 @@
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
"""
This module contains ...
"""
from zope.interface import implementer
import twisted
from twisted.conch import avatar
from twisted.conch.interfaces import IConchUser, ISession, ISFTPServer
from twisted.conch.ssh import filetransfer as conchfiletransfer
from twisted.python import log, components
from cowrie.core import pwd
from cowrie.ssh import session
from cowrie.ssh import filetransfer
from cowrie.ssh import forwarding
@implementer(IConchUser)
class CowrieUser(avatar.ConchUser):
"""
"""
def __init__(self, username, server):
avatar.ConchUser.__init__(self)
self.username = username
self.server = server
self.cfg = self.server.cfg
self.channelLookup.update(
{"session": session.HoneyPotSSHSession,
"direct-tcpip": forwarding.CowrieOpenConnectForwardingClient})
try:
pwentry = pwd.Passwd(self.cfg).getpwnam(self.username)
self.uid = pwentry["pw_uid"]
self.gid = pwentry["pw_gid"]
self.home = pwentry["pw_dir"]
except:
self.uid = 1001
self.gid = 1001
self.home = '/home'
# Sftp support enabled only when option is explicitly set
try:
if (self.cfg.get('honeypot', 'sftp_enabled') == "true"):
self.subsystemLookup['sftp'] = conchfiletransfer.FileTransferServer
except:
pass
def logout(self):
"""
"""
log.msg('avatar {} logging out'.format(self.username))
components.registerAdapter(filetransfer.SFTPServerForCowrieUser, CowrieUser, ISFTPServer)
components.registerAdapter(session.SSHSessionForCowrieUser, CowrieUser, ISession)

View File

@@ -130,4 +130,3 @@ class HoneypotPasswordChecker:
username=theusername, password=thepassword)
return False
# vim: set sw=4 et:

View File

@@ -12,4 +12,3 @@ def readConfigFile(cfgfile):
cfg.readfp(open(cfgfile))
return cfg
# vim: set sw=4 et:

View File

@@ -100,4 +100,3 @@ class UsernamePasswordIP:
self.password = password
self.ip = ip
# vim: set sw=4 et:

View File

@@ -195,4 +195,3 @@ class DBLogger(object):
def handleFileDownload(self, session, args):
pass
# vim: set sw=4 et:

View File

@@ -42,6 +42,7 @@ class TooManyLevels(Exception):
class FileNotFound(Exception):
"""
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
"""
pass
@@ -561,4 +562,3 @@ class _statobj:
self.st_mtime = st_mtime
self.st_ctime = st_ctime
# vim: set sw=4 et:

View File

@@ -31,7 +31,6 @@ class HoneyPotCommand(object):
# MS-DOS style redirect handling, inside the command
if '>' in self.args:
self.writtenBytes = 0
self.writeln = self.writeToFileLn
self.write = self.writeToFile
index = self.args.index(">")
@@ -45,7 +44,6 @@ class HoneyPotCommand(object):
self.fs.update_realfile(self.fs.getfile(self.outfile), self.safeoutfile)
else:
self.write = self.protocol.terminal.write
self.writeln = self.protocol.writeln
def writeToFile(self, data):
@@ -57,12 +55,6 @@ class HoneyPotCommand(object):
self.fs.update_size(self.outfile, self.writtenBytes)
def writeToFileLn(self, data):
"""
"""
self.writeToFile(data+'\n')
def start(self):
"""
"""
@@ -130,10 +122,11 @@ class HoneyPotShell(object):
def __init__(self, protocol, interactive=True):
self.protocol = protocol
self.interactive = interactive
self.showPrompt()
self.cmdpending = []
self.environ = protocol.environ
self.showPrompt()
def lineReceived(self, line):
"""
@@ -260,6 +253,7 @@ class HoneyPotShell(object):
attrs = {'path': path}
self.protocol.terminal.write(prompt % attrs)
self.protocol.ps = (prompt % attrs , '> ')
def handle_CTRL_C(self):

View File

@@ -206,4 +206,3 @@ def makeInteractFactory(honeypotFactory):
ifactory.honeypotFactory = honeypotFactory
return ifactory
# vim: set sw=4 et:

View File

@@ -61,4 +61,3 @@ def getDSAKeys(cfg):
privateKeyString = f.read()
return publicKeyString, privateKeyString

View File

@@ -169,4 +169,3 @@ class Output(object):
del self.sessions[sessionno]
del self.ips[sessionno]
# vim: set sw=4 et:

View File

@@ -91,7 +91,7 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol, TimeoutMixin):
"""
this logs out when connection times out
"""
self.write( 'timed out waiting for input: auto-logout\n' )
self.terminal.write( 'timed out waiting for input: auto-logout\n' )
ret = failure.Failure(error.ProcessTerminated(exitCode=1))
self.terminal.transport.processEnded(ret)
@@ -150,12 +150,15 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol, TimeoutMixin):
if self.fs.exists(i):
path = i
break
txt = os.path.normpath('%s/%s' % \
(self.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
@@ -167,14 +170,6 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol, TimeoutMixin):
self.cmdstack[-1].lineReceived(line)
def writeln(self, data):
"""
Sometimes still called after disconnect because of a deferred
"""
if self.terminal:
self.terminal.write(data+'\n')
def call_command(self, cmd, *args):
"""
"""
@@ -246,6 +241,7 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin
'\x06': self.handle_RIGHT, # CTRL-F
'\x09': self.handle_TAB,
'\x0B': self.handle_CTRL_K, # CTRL-K
'\x0C': self.handle_CTRL_L, # CTRL-L
'\x0E': self.handle_DOWN, # CTRL-N
'\x10': self.handle_UP, # CTRL-P
'\x15': self.handle_CTRL_U, # CTRL-U
@@ -256,7 +252,7 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin
"""
"""
try:
self.write(self.fs.file_contents('/etc/motd')+'\n')
self.terminal.write(self.fs.file_contents('/etc/motd')+'\n')
except:
pass
@@ -348,6 +344,16 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin
self.lineBuffer = self.lineBuffer[0:self.lineBufferIndex]
def handle_CTRL_L(self):
"""
Handle a 'form feed' byte - generally used to request a screen
refresh/redraw.
"""
self.terminal.eraseDisplay()
self.terminal.cursorHome()
self.drawInputLine()
def handle_CTRL_U(self):
"""
"""
@@ -357,152 +363,3 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin
self.lineBuffer = self.lineBuffer[self.lineBufferIndex:]
self.lineBufferIndex = 0
class LoggingServerProtocol(insults.ServerProtocol):
"""
Wrapper for ServerProtocol that implements TTY logging
"""
def __init__(self, prot=None, *a, **kw):
insults.ServerProtocol.__init__(self, prot, *a, **kw)
self.cfg = a[0].cfg
self.bytesReceived = 0
self.interactors = []
try:
self.bytesReceivedLimit = int(self.cfg.get('honeypot', 'download_limit_size'))
except:
self.bytesReceivedLimit = 0
if prot is HoneyPotExecProtocol:
self.type = 'e' # execcmd
else:
self.type = 'i' # interactive
def connectionMade(self):
"""
"""
transportId = self.transport.session.conn.transport.transportId
channelId = self.transport.session.id
self.ttylog_file = '%s/tty/%s-%s-%s%s.log' % \
(self.cfg.get('honeypot', 'log_path'),
time.strftime('%Y%m%d-%H%M%S'), transportId, channelId,
self.type)
ttylog.ttylog_open(self.ttylog_file, time.time())
self.ttylog_open = True
log.msg(eventid='COW0004', ttylog=self.ttylog_file,
format='Opening TTY Log: %(ttylog)s')
self.stdinlog_file = '%s/%s-%s-%s-stdin.log' % \
(self.cfg.get('honeypot', 'download_path'),
time.strftime('%Y%m%d-%H%M%S'), transportId, channelId)
self.stdinlog_open = False
insults.ServerProtocol.connectionMade(self)
def write(self, bytes):
"""
"""
for i in self.interactors:
i.sessionWrite(bytes)
if self.ttylog_open:
ttylog.ttylog_write(self.ttylog_file, len(bytes),
ttylog.TYPE_OUTPUT, time.time(), bytes)
insults.ServerProtocol.write(self, bytes)
def dataReceived(self, data):
"""
"""
self.bytesReceived += len(data)
if self.bytesReceivedLimit and self.bytesReceived > self.bytesReceivedLimit:
log.msg(eventid='COW0015', format='Data upload limit reached')
#self.loseConnection()
self.eofReceived()
return
if self.stdinlog_open:
with file(self.stdinlog_file, 'ab') as f:
f.write(data)
elif self.ttylog_open:
ttylog.ttylog_write(self.ttylog_file, len(data),
ttylog.TYPE_INPUT, time.time(), data)
insults.ServerProtocol.dataReceived(self, data)
def eofReceived(self):
"""
"""
if self.terminalProtocol:
self.terminalProtocol.eofReceived()
def addInteractor(self, interactor):
"""
"""
self.interactors.append(interactor)
def delInteractor(self, interactor):
"""
"""
self.interactors.remove(interactor)
def loseConnection(self):
"""
override super to remove the terminal reset on logout
"""
self.transport.loseConnection()
def connectionLost(self, reason):
"""
FIXME: this method is called 4 times on logout....
it's called once from Avatar.closed() if disconnected
"""
log.msg("received call to LSP.connectionLost")
for i in self.interactors:
i.sessionClosed()
transport = self.transport.session.conn.transport
if self.stdinlog_open:
try:
with open(self.stdinlog_file, 'rb') as f:
shasum = hashlib.sha256(f.read()).hexdigest()
shasumfile = self.cfg.get('honeypot',
'download_path') + "/" + shasum
if (os.path.exists(shasumfile)):
os.remove(self.stdinlog_file)
else:
os.rename(self.stdinlog_file, shasumfile)
os.symlink(shasum, self.stdinlog_file)
log.msg(eventid='COW0007',
format='Saved stdin contents to %(outfile)s',
url='stdin', outfile=shasumfile, shasum=shasum)
except IOError as e:
pass
finally:
self.stdinlog_open = False
if self.ttylog_open:
log.msg(eventid='COW0012', format='Closing TTY Log: %(ttylog)s',
ttylog=self.ttylog_file)
ttylog.ttylog_close(self.ttylog_file, time.time())
self.ttylog_open = False
self.cfg = None
insults.ServerProtocol.connectionLost(self, reason)
# vim: set sw=4 et:

View File

@@ -190,4 +190,3 @@ class Group(object):
return _
raise KeyError("getgruid(): uid not found in group file: " + uid)
# vim: set sw=4 et:

View File

@@ -38,7 +38,7 @@ from twisted.python import log
from cowrie.core import protocol
from cowrie.core import server
from cowrie.core import ssh
from cowrie.core import avatar
import sys
import gc
@@ -71,8 +71,7 @@ class HoneyPotRealm:
if conchinterfaces.IConchUser in interfaces:
return interfaces[0], \
ssh.CowrieUser(avatarId, server.CowrieServer(self.cfg)), lambda:None
avatar.CowrieUser(avatarId, server.CowrieServer(self.cfg)), lambda:None
else:
raise Exception("No supported interfaces found.")

View File

@@ -38,4 +38,3 @@ def ttylog_close(logfile, stamp):
sec, usec = int(stamp), int(1000000 * (stamp - int(stamp)))
f.write(struct.pack('<iLiiLL', 2, 0, 0, 0, sec, usec))
# vim: set sw=4 et:

View File

@@ -87,5 +87,3 @@ def uptime(total_seconds):
s += '%s min' % (str(minutes))
return s
# vim: set sw=4 et:

View File

@@ -1,27 +1,30 @@
from twisted.words.xish import domish
from twisted.python import log
from wokkel.xmppim import AvailablePresence
from twisted.words.protocols.jabber.jid import JID
from wokkel import muc
import uuid
import json
class XMPPLoggerProtocol(muc.MUCClient):
def __init__(self, server, rooms, nick):
def __init__(self, rooms, server, nick):
muc.MUCClient.__init__(self)
self.server = server
self.jrooms = rooms
self.server = rooms.host
self.jrooms = rooms
self._roomOccupantMap = {}
log.msg(rooms.user)
log.msg(rooms.host)
self.nick = nick
self.last = {}
self.activity = None
def initialized(self):
def connectionInitialized(self):
"""The bot has connected to the xmpp server, now try to join the room.
"""
for i in self.jrooms:
print(i)
self.join(self.server, i, self.nick).addCallback(self.initRoom)
self.join(self.jrooms, self.nick);
def initRoom(self, room):
def joinedRoom(self, room):
log.msg( 'Joined room %s' % room.name )
def connectionMade(self):
@@ -31,7 +34,7 @@ class XMPPLoggerProtocol(muc.MUCClient):
self.send(AvailablePresence())
def connectionLost(self, reason):
logmsg( 'Disconnected!' )
log.msg( 'Disconnected!' )
def onMessage(self, msg):
pass
@@ -69,10 +72,11 @@ class DBLogger(dblog.DBLogger):
for i in range(8)])
jid = user + '/' + resource
application = service.Application('honeypot')
self.run(application, jid, password, muc, channels)
self.run(application, jid, password, JID(None,[muc,server,None]), channels)
def run(self, application, jidstr, password, muc, channels, anon=True):
self.xmppclient = XMPPClient(jid.JID(jidstr), password)
self.xmppclient = XMPPClient(JID(jidstr), password)
if self.cfg.has_option('database_xmpp', 'debug') and \
self.cfg.get('database_xmpp', 'debug') in ('1', 'true', 'yes'):
self.xmppclient.logTraffic = True # DEBUG HERE
@@ -94,18 +98,16 @@ class DBLogger(dblog.DBLogger):
(self.signals[msgtype], self.muc.server) , msg)
def report(self, msgtype, to, xmsg):
body = domish.Element((None, 'body'))
body.addContent('\n')
msg = domish.Element(('http://github.com/micheloosterhof/cowrie', 'cowrie'))
msg = {}
msg['type'] = msgtype
msg.addChild(xmsg)
body.addChild(msg)
self.muc.groupChat(jid.JID(to), body)
msg['message'] = xmsg
msgJson = json.dumps(msg,indent=5)
self.muc.groupChat(self.muc.jrooms, msgJson)
# We have to return an unique ID
def createSession(self, peerIP, peerPort, hostIP, hostPort):
session = uuid.uuid4().hex
ses = domish.Element((None, 'session'))
ses = {}
ses['session'] = session
ses['remote_host'] = peerIP
ses['remote_port'] = str(peerPort)
@@ -122,50 +124,50 @@ class DBLogger(dblog.DBLogger):
pass
def handleConnectionLost(self, session, args):
ses = domish.Element((None, 'session'))
ses = {}
ses['session'] = session
self.broadcast('connectionlost', ses)
def handleLoginFailed(self, session, args):
ses = domish.Element((None, 'credentials'))
ses = {}
ses['session'] = session
ses['username'] = args['username']
ses['password'] = args['password']
self.broadcast('loginfailed', ses)
def handleLoginSucceeded(self, session, args):
ses = domish.Element((None, 'credentials'))
ses = {}
ses['session'] = session
ses['username'] = args['username']
ses['password'] = args['password']
self.broadcast('loginsucceeded', ses)
def handleCommand(self, session, args):
ses = domish.Element((None, 'command'))
ses = {}
ses['session'] = session
ses['command'] = 'known'
ses.addContent(args['input'])
ses['input'] = args['input']
self.broadcast('command', ses)
def handleUnknownCommand(self, session, args):
ses = domish.Element((None, 'command'))
ses = {}
ses['session'] = session
ses['command'] = 'unknown'
ses.addContent(args['input'])
ses['input'] = args['input']
self.broadcast('command', ses)
def handleInput(self, session, args):
ses = domish.Element((None, 'input'))
ses = {}
ses['session'] = session
ses['realm'] = args['realm']
ses.addContent(args['input'])
ses['input'] = args['input']
self.broadcast('input', ses)
def handleTerminalSize(self, session, args):
pass
def handleClientVersion(self, session, args):
ses = domish.Element((None, 'version'))
ses = {}
ses['session'] = session
ses['version'] = args['version']
self.broadcast('clientversion', ses)

View File

164
cowrie/insults/insults.py Normal file
View File

@@ -0,0 +1,164 @@
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
"""
This module contains ...
"""
import os
import time
import hashlib
from twisted.python import log
from twisted.conch.insults import insults
from cowrie.core import ttylog
from cowrie.core import protocol
class LoggingServerProtocol(insults.ServerProtocol):
"""
Wrapper for ServerProtocol that implements TTY logging
"""
def __init__(self, prot=None, *a, **kw):
insults.ServerProtocol.__init__(self, prot, *a, **kw)
self.cfg = a[0].cfg
self.bytesReceived = 0
self.interactors = []
try:
self.bytesReceivedLimit = int(self.cfg.get('honeypot', 'download_limit_size'))
except:
self.bytesReceivedLimit = 0
if prot is protocol.HoneyPotExecProtocol:
self.type = 'e' # execcmd
else:
self.type = 'i' # interactive
def connectionMade(self):
"""
"""
transportId = self.transport.session.conn.transport.transportId
channelId = self.transport.session.id
self.ttylog_file = '%s/tty/%s-%s-%s%s.log' % \
(self.cfg.get('honeypot', 'log_path'),
time.strftime('%Y%m%d-%H%M%S'), transportId, channelId,
self.type)
ttylog.ttylog_open(self.ttylog_file, time.time())
self.ttylog_open = True
log.msg(eventid='COW0004', ttylog=self.ttylog_file,
format='Opening TTY Log: %(ttylog)s')
self.stdinlog_file = '%s/%s-%s-%s-stdin.log' % \
(self.cfg.get('honeypot', 'download_path'),
time.strftime('%Y%m%d-%H%M%S'), transportId, channelId)
self.stdinlog_open = False
insults.ServerProtocol.connectionMade(self)
def write(self, bytes):
"""
"""
for i in self.interactors:
i.sessionWrite(bytes)
if self.ttylog_open:
ttylog.ttylog_write(self.ttylog_file, len(bytes),
ttylog.TYPE_OUTPUT, time.time(), bytes)
insults.ServerProtocol.write(self, bytes)
def dataReceived(self, data):
"""
"""
self.bytesReceived += len(data)
if self.bytesReceivedLimit and self.bytesReceived > self.bytesReceivedLimit:
log.msg(eventid='COW0015', format='Data upload limit reached')
#self.loseConnection()
self.eofReceived()
return
if self.stdinlog_open:
with file(self.stdinlog_file, 'ab') as f:
f.write(data)
elif self.ttylog_open:
ttylog.ttylog_write(self.ttylog_file, len(data),
ttylog.TYPE_INPUT, time.time(), data)
insults.ServerProtocol.dataReceived(self, data)
def eofReceived(self):
"""
"""
if self.terminalProtocol:
self.terminalProtocol.eofReceived()
def addInteractor(self, interactor):
"""
"""
self.interactors.append(interactor)
def delInteractor(self, interactor):
"""
"""
self.interactors.remove(interactor)
def loseConnection(self):
"""
override super to remove the terminal reset on logout
"""
self.transport.loseConnection()
def connectionLost(self, reason):
"""
FIXME: this method is called 4 times on logout....
it's called once from Avatar.closed() if disconnected
"""
log.msg("received call to LSP.connectionLost")
for i in self.interactors:
i.sessionClosed()
transport = self.transport.session.conn.transport
if self.stdinlog_open:
try:
with open(self.stdinlog_file, 'rb') as f:
shasum = hashlib.sha256(f.read()).hexdigest()
shasumfile = self.cfg.get('honeypot',
'download_path') + "/" + shasum
if (os.path.exists(shasumfile)):
os.remove(self.stdinlog_file)
else:
os.rename(self.stdinlog_file, shasumfile)
os.symlink(shasum, self.stdinlog_file)
log.msg(eventid='COW0007',
format='Saved stdin contents to %(outfile)s',
url='stdin', outfile=shasumfile, shasum=shasum)
except IOError as e:
pass
finally:
self.stdinlog_open = False
if self.ttylog_open:
log.msg(eventid='COW0012', format='Closing TTY Log: %(ttylog)s',
ttylog=self.ttylog_file)
ttylog.ttylog_close(self.ttylog_file, time.time())
self.ttylog_open = False
self.cfg = None
insults.ServerProtocol.connectionLost(self, reason)

0
cowrie/ssh/__init__.py Normal file
View File

View File

@@ -10,209 +10,14 @@ import os
from zope.interface import implementer
import twisted
from twisted.conch import avatar, interfaces as conchinterfaces
from twisted.conch.ssh import session
from twisted.conch.interfaces import ISFTPFile, ISFTPServer
from twisted.conch.ssh import filetransfer
from twisted.conch.ssh import forwarding
from twisted.conch.ssh.filetransfer import FXF_READ, FXF_WRITE, FXF_APPEND, FXF_CREAT, FXF_TRUNC, FXF_EXCL
import twisted.conch.ls
from twisted.python import log, components
from twisted.conch.ssh.common import getNS
from cowrie.core import pwd
from cowrie.core import protocol
from twisted.python import log
class HoneyPotSSHSession(session.SSHSession):
"""
This is an SSH channel that's used for SSH sessions
"""
def __init__(self, *args, **kw):
session.SSHSession.__init__(self, *args, **kw)
#self.__dict__['request_auth_agent_req@openssh.com'] = self.request_agent
def request_env(self, data):
"""
"""
name, rest = getNS(data)
value, rest = getNS(rest)
if rest:
raise ValueError("Bad data given in env request")
log.msg(eventid='COW0013', format='request_env: %(name)s=%(value)s',
name=name, value=value)
# Environment variables come after shell or before exec command
if self.session:
self.session.environ[name] = value
return 0
def request_agent(self, data):
"""
"""
log.msg('request_agent: %s' % (repr(data),))
return 0
def request_x11_req(self, data):
"""
"""
log.msg('request_x11: %s' % (repr(data),))
return 0
def closed(self):
"""
This is reliably called on session close/disconnect and calls the avatar
"""
session.SSHSession.closed(self)
def sendEOF(self):
"""
Utility function to request to send EOF for this session
"""
self.conn.sendEOF(self)
def sendClose(self):
"""
Utility function to request to send close for this session
"""
self.conn.sendClose(self)
def channelClosed(self):
"""
"""
log.msg("Called channelClosed in SSHSession")
@implementer(conchinterfaces.IConchUser)
class CowrieUser(avatar.ConchUser):
"""
"""
def __init__(self, username, server):
avatar.ConchUser.__init__(self)
self.username = username
self.server = server
self.cfg = self.server.cfg
self.channelLookup.update(
{"session": HoneyPotSSHSession,
"direct-tcpip": CowrieOpenConnectForwardingClient})
try:
pwentry = pwd.Passwd(self.cfg).getpwnam(self.username)
self.uid = pwentry["pw_uid"]
self.gid = pwentry["pw_gid"]
self.home = pwentry["pw_dir"]
except:
self.uid = 1001
self.gid = 1001
self.home = '/home'
# Sftp support enabled only when option is explicitly set
try:
if (self.cfg.get('honeypot', 'sftp_enabled') == "true"):
self.subsystemLookup['sftp'] = filetransfer.FileTransferServer
except:
pass
def logout(self):
"""
"""
log.msg('avatar {} logging out'.format(self.username))
@implementer(conchinterfaces.ISession)
class SSHSessionForCowrieUser:
"""
"""
def __init__(self, avatar, reactor=None):
"""
Construct an C{SSHSessionForCowrieUser}.
@param avatar: The L{CowrieUser} for whom this is an SSH session.
@param reactor: An L{IReactorProcess} used to handle shell and exec
requests. Uses the default reactor if None.
"""
self.protocol = None
self.avatar = avatar
self.server = avatar.server
self.cfg = avatar.cfg
self.uid = avatar.uid
self.gid = avatar.gid
self.username = avatar.username
self.environ = {
'LOGNAME': self.username,
'USER': self.username,
'HOME': self.avatar.home}
if self.uid==0:
self.environ['PATH']='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
else:
self.environ['PATH']='/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games'
def openShell(self, processprotocol):
"""
"""
self.protocol = protocol.LoggingServerProtocol(
protocol.HoneyPotInteractiveProtocol, self)
self.protocol.makeConnection(processprotocol)
processprotocol.makeConnection(session.wrapProtocol(self.protocol))
def getPty(self, terminal, windowSize, attrs):
"""
"""
self.environ['TERM'] = terminal
log.msg(eventid='COW0010', width=windowSize[0], height=windowSize[1],
format='Terminal Size: %(width)s %(height)s')
self.windowSize = windowSize
return None
def execCommand(self, processprotocol, cmd):
"""
"""
self.protocol = protocol.LoggingServerProtocol(
protocol.HoneyPotExecProtocol, self, cmd)
self.protocol.makeConnection(processprotocol)
processprotocol.makeConnection(session.wrapProtocol(self.protocol))
def closed(self):
"""
this is reliably called on both logout and disconnect
we notify the protocol here we lost the connection
"""
if self.protocol:
self.protocol.connectionLost("disconnected")
self.protocol = None
def eofReceived(self):
"""
"""
if self.protocol:
self.protocol.eofReceived()
def windowChanged(self, windowSize):
"""
"""
self.windowSize = windowSize
@implementer(conchinterfaces.ISFTPFile)
@implementer(ISFTPFile)
class CowrieSFTPFile:
"""
"""
@@ -335,7 +140,7 @@ class CowrieSFTPDirectory:
@implementer(conchinterfaces.ISFTPServer)
@implementer(ISFTPServer)
class SFTPServerForCowrieUser:
"""
"""
@@ -476,39 +281,3 @@ class SFTPServerForCowrieUser:
"""
raise NotImplementedError
components.registerAdapter(SFTPServerForCowrieUser, CowrieUser, conchinterfaces.ISFTPServer)
components.registerAdapter(SSHSessionForCowrieUser, CowrieUser, session.ISession)
def CowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar):
"""
"""
remoteHP, origHP = twisted.conch.ssh.forwarding.unpackOpen_direct_tcpip(data)
log.msg(eventid='COW0014', format='direct-tcp connection request to %(dst_ip)s:%(dst_port)s',
dst_ip=remoteHP[0], dst_port=remoteHP[1])
return CowrieConnectForwardingChannel(remoteHP,
remoteWindow=remoteWindow, remoteMaxPacket=remoteMaxPacket,
avatar=avatar)
class CowrieConnectForwardingChannel(forwarding.SSHConnectForwardingChannel):
"""
"""
def channelOpen(self, specificData):
"""
"""
pass
def dataReceived(self, data):
"""
"""
log.msg(eventid='COW0015',
format='direct-tcp forward to %(dst_ip)s:%(dst_port)s with data %(data)s',
dst_ip=self.hostport[0], dst_port=self.hostport[1], data=repr(data))
self._close("Connection refused")
# vim: set et sw=4 et:

41
cowrie/ssh/forwarding.py Normal file
View File

@@ -0,0 +1,41 @@
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
"""
This module contains ...
"""
import twisted
from twisted.conch.ssh import forwarding
from twisted.python import log
def CowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar):
"""
"""
remoteHP, origHP = twisted.conch.ssh.forwarding.unpackOpen_direct_tcpip(data)
log.msg(eventid='COW0014', format='direct-tcp connection request to %(dst_ip)s:%(dst_port)s',
dst_ip=remoteHP[0], dst_port=remoteHP[1])
return CowrieConnectForwardingChannel(remoteHP,
remoteWindow=remoteWindow, remoteMaxPacket=remoteMaxPacket,
avatar=avatar)
class CowrieConnectForwardingChannel(forwarding.SSHConnectForwardingChannel):
"""
"""
def channelOpen(self, specificData):
"""
"""
pass
def dataReceived(self, data):
"""
"""
log.msg(eventid='COW0015',
format='direct-tcp forward to %(dst_ip)s:%(dst_port)s with data %(data)s',
dst_ip=self.hostport[0], dst_port=self.hostport[1], data=repr(data))
self._close("Connection refused")

169
cowrie/ssh/session.py Normal file
View File

@@ -0,0 +1,169 @@
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
"""
This module contains ...
"""
import os
from zope.interface import implementer
import twisted
from twisted.conch.interfaces import ISession
from twisted.conch.ssh import session
from twisted.python import log
from twisted.conch.ssh.common import getNS
from cowrie.core import protocol
from cowrie.core import pwd
from cowrie.insults import insults
class HoneyPotSSHSession(session.SSHSession):
"""
This is an SSH channel that's used for SSH sessions
"""
def __init__(self, *args, **kw):
session.SSHSession.__init__(self, *args, **kw)
#self.__dict__['request_auth_agent_req@openssh.com'] = self.request_agent
def request_env(self, data):
"""
"""
name, rest = getNS(data)
value, rest = getNS(rest)
if rest:
raise ValueError("Bad data given in env request")
log.msg(eventid='COW0013', format='request_env: %(name)s=%(value)s',
name=name, value=value)
# FIXME: This only works for shell, not for exec command
if self.session:
self.session.environ[name] = value
return 0
def request_agent(self, data):
"""
"""
log.msg('request_agent: %s' % (repr(data),))
return 0
def request_x11_req(self, data):
"""
"""
log.msg('request_x11: %s' % (repr(data),))
return 0
def closed(self):
"""
This is reliably called on session close/disconnect and calls the avatar
"""
session.SSHSession.closed(self)
def sendEOF(self):
"""
Utility function to request to send EOF for this session
"""
self.conn.sendEOF(self)
def sendClose(self):
"""
Utility function to request to send close for this session
"""
self.conn.sendClose(self)
def channelClosed(self):
"""
"""
log.msg("Called channelClosed in SSHSession")
@implementer(ISession)
class SSHSessionForCowrieUser:
"""
"""
def __init__(self, avatar, reactor=None):
"""
Construct an C{SSHSessionForCowrieUser}.
@param avatar: The L{CowrieUser} for whom this is an SSH session.
@param reactor: An L{IReactorProcess} used to handle shell and exec
requests. Uses the default reactor if None.
"""
self.protocol = None
self.avatar = avatar
self.server = avatar.server
self.cfg = avatar.cfg
self.uid = avatar.uid
self.gid = avatar.gid
self.username = avatar.username
self.environ = {
'LOGNAME': self.username,
'USER': self.username,
'HOME': self.avatar.home,
'TMOUT': '1800'}
if self.uid==0:
self.environ['PATH']='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
else:
self.environ['PATH']='/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games'
def openShell(self, processprotocol):
"""
"""
self.protocol = insults.LoggingServerProtocol(
protocol.HoneyPotInteractiveProtocol, self)
self.protocol.makeConnection(processprotocol)
processprotocol.makeConnection(session.wrapProtocol(self.protocol))
def getPty(self, terminal, windowSize, attrs):
"""
"""
self.environ['TERM'] = terminal
log.msg(eventid='COW0010', width=windowSize[0], height=windowSize[1],
format='Terminal Size: %(width)s %(height)s')
self.windowSize = windowSize
return None
def execCommand(self, processprotocol, cmd):
"""
"""
self.protocol = insults.LoggingServerProtocol(
protocol.HoneyPotExecProtocol, self, cmd)
self.protocol.makeConnection(processprotocol)
processprotocol.makeConnection(session.wrapProtocol(self.protocol))
def closed(self):
"""
this is reliably called on both logout and disconnect
we notify the protocol here we lost the connection
"""
if self.protocol:
self.protocol.connectionLost("disconnected")
self.protocol = None
def eofReceived(self):
"""
"""
if self.protocol:
self.protocol.eofReceived()
def windowChanged(self, windowSize):
"""
"""
self.windowSize = windowSize

View File

@@ -17,8 +17,8 @@ from twisted.conch.openssh_compat import primes
from twisted.conch.ssh.common import getNS
from twisted.protocols.policies import TimeoutMixin
from cowrie.core import connection
from cowrie.core import userauth
from cowrie.ssh import connection
from cowrie.ssh import userauth
from cowrie.core import keys as cowriekeys
@@ -291,4 +291,3 @@ class HoneyPotTransport(transport.SSHServerTransport, TimeoutMixin):
% (reason, desc))
self.transport.loseConnection()

View File

@@ -45,10 +45,11 @@ from twisted.cred import portal
from cowrie.core.config import readConfigFile
from cowrie import core
import cowrie.core.transport
import cowrie.core.realm
import cowrie.core.checkers
import cowrie.ssh.transport
class Options(usage.Options):
"""
FIXME: Docstring
@@ -80,6 +81,25 @@ class CowrieServiceMaker(object):
cfg = readConfigFile(options["config"])
top_service = service.MultiService()
application = service.Application('cowrie')
top_service.setServiceParent(application)
factory = cowrie.ssh.transport.HoneyPotSSHFactory(cfg)
factory.portal = portal.Portal(core.realm.HoneyPotRealm(cfg))
factory.portal.registerChecker(
core.checkers.HoneypotPublicKeyChecker())
factory.portal.registerChecker(
core.checkers.HoneypotPasswordChecker(cfg))
if cfg.has_option('honeypot', 'auth_none_enabled') and \
cfg.get('honeypot', 'auth_none_enabled').lower() in \
('yes', 'true', 'on'):
factory.portal.registerChecker(
core.checkers.HoneypotNoneChecker())
if cfg.has_option('honeypot', 'listen_addr'):
listen_addr = cfg.get('honeypot', 'listen_addr')
else:
@@ -93,23 +113,9 @@ class CowrieServiceMaker(object):
else:
listen_port = 2222
factory = core.transport.HoneyPotSSHFactory(cfg)
factory.portal = portal.Portal(core.realm.HoneyPotRealm(cfg))
factory.portal.registerChecker(
core.checkers.HoneypotPublicKeyChecker())
factory.portal.registerChecker(
core.checkers.HoneypotPasswordChecker(cfg))
if cfg.has_option('honeypot', 'auth_none_enabled') and \
cfg.get('honeypot', 'auth_none_enabled').lower() in \
('yes', 'true', 'on'):
factory.portal.registerChecker(
core.checkers.HoneypotNoneChecker())
top_service = top_service = service.MultiService()
for i in listen_addr.split():
svc = internet.TCPServer(listen_port, factory, interface=i)
# FIXME: Use addService on top_service ?
svc.setServiceParent(top_service)
if cfg.has_option('honeypot', 'interact_enabled') and \
@@ -118,11 +124,10 @@ class CowrieServiceMaker(object):
iport = int(cfg.get('honeypot', 'interact_port'))
from cowrie.core import interact
svc = internet.TCPServer(iport,
interact.makeInteractFactory(factory))
interact.makeInteractFactory(factory), interface='127.0.0.1')
# FIXME: Use addService on top_service ?
svc.setServiceParent(top_service)
application = service.Application('cowrie')
top_service.setServiceParent(application)
return top_service
# Now construct an object which *provides* the relevant interfaces