Files
cowrie/cowrie/core/protocol.py
fe7ch e2033c36f3 Append redirection support (#428)
* Add support for '>>' redirection

* Add redir files hashing

* Delete only ">" or ">>" + file name from cmd args

* Update stdin/redir messages to include SHA-256 hash of the file content

* Small style fixes, log if we don't store duplicate

* Bug fixes for wget command

* Use os.path.join instead of string formatting
* Use "with" for hashing a file to prevent handle leakage
* Don't overwrite self.safeoutfile if it was already set in HoneyPotyCommand's init method
* Don't overwrite self.safeoutfile with hash, else it will break stuff in insults.py

* Revert "Delete only ">" or ">>" + file name from cmd args"

This reverts commit f3f8b90cbe221da8ffba2670f4419da105ad8ac3.

* Fix bugged check for presence of safeoutfile attribute.

* Don't overwrite safeoutfile in curl

* Don't store None objects

* Include transportId and sessionId to all safeoutfiles to avoid collisions.
2017-02-01 02:53:31 +08:00

435 lines
12 KiB
Python

# -*- test-case-name: cowrie.test.protocol -*-
# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
# See the COPYRIGHT file for more information
"""
This module contains ...
"""
import os
import time
import socket
from twisted.python import failure, log
from twisted.internet import error
from twisted.protocols.policies import TimeoutMixin
from twisted.conch import recvline
from twisted.conch.insults import insults
from cowrie.core import honeypot
from cowrie.core import utils
class HoneyPotBaseProtocol(insults.TerminalProtocol, TimeoutMixin):
"""
Base protocol for interactive and non-interactive use
"""
def __init__(self, avatar):
self.user = avatar
self.environ = avatar.environ
self.cfg = self.user.cfg
self.hostname = avatar.server.hostname
self.fs = avatar.server.fs
self.pp = None
self.logintime = None
self.realClientIP = None
self.realClientPort = None
self.clientVersion = None
self.kippoIP = None
self.clientIP = None
if self.fs.exists(avatar.avatar.home):
self.cwd = avatar.avatar.home
else:
self.cwd = '/'
self.input_data = None
self.data = None
self.commands = {}
import cowrie.commands
for c in cowrie.commands.__all__:
module = __import__('cowrie.commands.%s' % (c,),
globals(), locals(), ['commands'])
self.commands.update(module.commands)
self.password_input = False
self.cmdstack = []
def getProtoTransport(self):
"""
Due to protocol nesting differences, we need provide how we grab
the proper transport to access underlying SSH information. Meant to be
overridden for other protocols.
"""
return self.terminal.transport.session.conn.transport
def logDispatch(self, *msg, **args):
"""
Send log directly to factory, avoiding normal log dispatch
"""
pt = self.getProtoTransport()
args['sessionno'] = pt.transport.sessionno
pt.factory.logDispatch(*msg, **args)
def connectionMade(self):
"""
"""
pt = self.getProtoTransport()
self.realClientIP = pt.transport.getPeer().host
self.realClientPort = pt.transport.getPeer().port
self.clientVersion = self.getClientVersion()
self.logintime = time.time()
self.setTimeout(180)
# Source IP of client in user visible reports (can be fake or real)
try:
self.clientIP = self.cfg.get('honeypot', 'fake_addr')
except:
self.clientIP = self.realClientIP
if self.cfg.has_option('honeypot', 'internet_facing_ip'):
self.kippoIP = self.cfg.get('honeypot', 'internet_facing_ip')
else:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
self.kippoIP = s.getsockname()[0]
except:
self.kippoIP = '192.168.0.1'
finally:
s.close()
def timeoutConnection(self):
"""
this logs out when connection times out
"""
ret = failure.Failure(error.ProcessTerminated(exitCode=1))
self.terminal.transport.processEnded(ret)
def eofReceived(self):
"""
this should probably not go through ctrl-d, but use processprotocol to close stdin
"""
log.msg("received eof, sending ctrl-d to command")
if len(self.cmdstack):
self.cmdstack[-1].handle_CTRL_D()
def connectionLost(self, reason):
"""
Called when the connection is shut down.
Clear any circular references here, and any external references to
this Protocol. The connection has been closed.
"""
self.setTimeout(None)
insults.TerminalProtocol.connectionLost(self, reason)
self.terminal = None # (this should be done by super above)
del self.cmdstack
del self.commands
self.fs = None
self.pp = None
self.cfg = None
self.user = None
self.environ = None
log.msg("honeypot terminal protocol connection lost {}".format(reason))
def txtcmd(self, txt):
"""
"""
class command_txtcmd(honeypot.HoneyPotCommand):
def call(self):
log.msg('Reading txtcmd from "{}"'.format(txt))
with open(txt, 'r') as f:
self.write(f.read())
return command_txtcmd
def getCommand(self, cmd, paths):
"""
"""
if not len(cmd.strip()):
return None
path = None
if cmd in self.commands:
return self.commands[cmd]
if cmd[0] in ('.', '/'):
path = self.fs.resolve_path(cmd, self.cwd)
if not self.fs.exists(path):
return None
else:
for i in ['%s/%s' % (self.fs.resolve_path(x, self.cwd), cmd) \
for x in paths]:
if self.fs.exists(i):
path = i
break
txt = os.path.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
def lineReceived(self, line):
"""
Line Received
"""
self.resetTimeout()
if len(self.cmdstack):
self.cmdstack[-1].lineReceived(line)
def call_command(self, pp, cmd, *args):
"""
"""
self.pp = pp
obj = cmd(self, *args)
obj.set_input_data(pp.input_data)
self.cmdstack.append(obj)
obj.start()
if hasattr(obj, 'safeoutfile'):
if obj.safeoutfile:
self.terminal.redirFiles.add(obj.safeoutfile)
if self.pp:
self.pp.outConnectionLost()
def uptime(self):
"""
Uptime
"""
pt = self.getProtoTransport()
r = time.time() - pt.factory.starttime
#if reset:
# pt.factory.starttime = reset
return r
def getClientVersion(self):
pt = self.getProtoTransport()
return pt.otherVersionString
class HoneyPotExecProtocol(HoneyPotBaseProtocol):
"""
"""
def __init__(self, avatar, execcmd):
self.execcmd = execcmd
HoneyPotBaseProtocol.__init__(self, avatar)
def connectionMade(self):
"""
"""
HoneyPotBaseProtocol.connectionMade(self)
self.setTimeout(60)
self.cmdstack = [honeypot.HoneyPotShell(self, interactive=False)]
self.cmdstack[0].lineReceived(self.execcmd)
class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLine):
"""
"""
def __init__(self, avatar):
recvline.HistoricRecvLine.__init__(self)
HoneyPotBaseProtocol.__init__(self, avatar)
def connectionMade(self):
"""
"""
self.displayMOTD()
HoneyPotBaseProtocol.connectionMade(self)
recvline.HistoricRecvLine.connectionMade(self)
self.cmdstack = [honeypot.HoneyPotShell(self)]
self.keyHandlers.update({
'\x01': self.handle_HOME, # CTRL-A
'\x02': self.handle_LEFT, # CTRL-B
'\x03': self.handle_CTRL_C, # CTRL-C
'\x04': self.handle_CTRL_D, # CTRL-D
'\x05': self.handle_END, # CTRL-E
'\x06': self.handle_RIGHT, # CTRL-F
'\x08': self.handle_BACKSPACE, # CTRL-H
'\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
'\x16': self.handle_CTRL_V, # CTRL-V
'\x1b': self.handle_ESC, # ESC
})
def displayMOTD(self):
"""
"""
try:
self.terminal.write(self.fs.file_contents('/etc/motd')+'\n')
except:
pass
def timeoutConnection(self):
"""
this logs out when connection times out
"""
self.terminal.write( 'timed out waiting for input: auto-logout\n' )
HoneyPotBaseProtocol.timeoutConnection(self)
def lastlogExit(self):
"""
"""
starttime = time.strftime('%a %b %d %H:%M',
time.localtime(self.logintime))
endtime = time.strftime('%H:%M',
time.localtime(time.time()))
duration = utils.durationHuman(time.time() - self.logintime)
with open( '%s/lastlog.txt' % (self.cfg.get('honeypot',
'log_path'),), 'a') as f:
f.write('root\tpts/0\t%s\t%s - %s (%s)\n' % \
(self.clientIP, starttime, endtime, duration))
def connectionLost(self, reason):
"""
"""
self.lastlogExit()
HoneyPotBaseProtocol.connectionLost(self, reason)
recvline.HistoricRecvLine.connectionLost(self, reason)
self.keyHandlers = None
def initializeScreen(self):
"""
Overriding super to prevent terminal.reset()
"""
self.setInsertMode()
def call_command(self, pp, cmd, *args):
"""
"""
self.pp = pp
self.setTypeoverMode()
HoneyPotBaseProtocol.call_command(self, pp, cmd, *args)
def characterReceived(self, ch, moreCharactersComing):
"""
Easier way to implement password input?
"""
if self.mode == 'insert':
self.lineBuffer.insert(self.lineBufferIndex, ch)
else:
self.lineBuffer[self.lineBufferIndex:self.lineBufferIndex+1] = [ch]
self.lineBufferIndex += 1
if not self.password_input:
self.terminal.write(ch)
def handle_RETURN(self):
"""
"""
if len(self.cmdstack) == 1:
if self.lineBuffer:
self.historyLines.append(''.join(self.lineBuffer))
self.historyPosition = len(self.historyLines)
return recvline.RecvLine.handle_RETURN(self)
def handle_CTRL_C(self):
"""
"""
if len(self.cmdstack):
self.cmdstack[-1].handle_CTRL_C()
def handle_CTRL_D(self):
"""
"""
if len(self.cmdstack):
self.cmdstack[-1].handle_CTRL_D()
def handle_TAB(self):
"""
"""
if len(self.cmdstack):
self.cmdstack[-1].handle_TAB()
def handle_CTRL_K(self):
"""
"""
self.terminal.eraseToLineEnd()
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):
"""
"""
for _ in range(self.lineBufferIndex):
self.terminal.cursorBackward()
self.terminal.deleteCharacter()
self.lineBuffer = self.lineBuffer[self.lineBufferIndex:]
self.lineBufferIndex = 0
def handle_CTRL_V(self):
"""
"""
pass
def handle_ESC(self):
"""
"""
pass
class HoneyPotInteractiveTelnetProtocol(HoneyPotInteractiveProtocol):
"""
Specialized HoneyPotInteractiveProtocol that provides Telnet specific
overrides.
"""
def __init__(self, avatar):
recvline.HistoricRecvLine.__init__(self)
HoneyPotInteractiveProtocol.__init__(self, avatar)
def getProtoTransport(self):
"""
Due to protocol nesting differences, we need to override how we grab
the proper transport to access underlying Telnet information.
"""
return self.terminal.transport.session.transport
def getClientVersion(self):
return 'Telnet'