This commit is contained in:
Michel Oosterhof
2018-03-27 12:12:08 +04:00
21 changed files with 443 additions and 102 deletions

3
.gitignore vendored
View File

@ -15,4 +15,5 @@ __pycache__/
twisted/plugins/dropin.cache
.DS_Store
_trial_temp
.project
.pydevproject

View File

@ -286,6 +286,19 @@ forward_redirect = false
# forward_redirect_587 = 127.0.0.1:12525
# This enables tunneling forwarding requests to another address
# Useful for forwarding protocols to a proxy like Squid
# (default: false)
forward_tunnel = false
# Configure where to tunnel the data to.
# forward_tunnel_<portnumber> = <tunnel ip>:<tunnel port>
# Tunnel http/https
# forward_tunnel_80 = 127.0.0.1:3128
# forward_tunnel_443 = 127.0.0.1:3128
# ============================================================================
# Telnet Specific Options
@ -531,7 +544,9 @@ logfile = log/cowrie.json
#
#[output_s3]
#
# The AWS credentials to use
# The AWS credentials to use.
# Leave these blank to use botocore's credential discovery e.g .aws/config or ENV variables.
# As per https://github.com/boto/botocore/blob/develop/botocore/credentials.py#L50-L65
#access_key_id = AKIDEXAMPLE
#secret_access_key = wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY
#
@ -558,7 +573,21 @@ logfile = log/cowrie.json
#[output_redis]
# Host of the redis server. Required
#host = 127.0.0.1
# Port of the redis server. Required
#port = 6379
# DB of the redis server. Defaults to 0
#db = 0
# Password of the redis server. Defaults to None
#password = secret
# Name of the list to push to or the channel to publish to. Required
#keyname = cowrie
# Method to use when sending data to redis.
# Can be one of [lpush, rpush, publish]. Defaults to lpush
#send_method = lpush

View File

@ -5,6 +5,7 @@ __all__ = [
'adduser',
'apt',
'base',
'base64',
'busybox',
'curl',
'dd',

View File

@ -140,7 +140,6 @@ class command_echo(HoneyPotCommand):
except:
args = self.args
# FIXME: Wrap in exception, Python escape cannot handle single digit \x codes (e.g. \x1)
try:
# replace r'\\x' with r'\x'
s = ' '.join(args).replace('\\\\x', '\\x')
@ -156,13 +155,14 @@ class command_echo(HoneyPotCommand):
s = s[:-2]
newline = False
if newline is True:
s += '\n'
self.write(escape_fn(s))
except ValueError as e:
log.msg("echo command received Python incorrect hex escape")
if newline is True:
self.write('\n')
commands['/bin/echo'] = command_echo
@ -705,4 +705,4 @@ commands['/bin/chgrp'] = command_nop
commands['/usr/bin/chattr'] = command_nop
commands[':'] = command_nop
# vim: set sw=4 et:
# vim: set sw=4 et:

126
cowrie/commands/base64.py Normal file
View File

@ -0,0 +1,126 @@
from __future__ import division, absolute_import
import getopt
from twisted.python import log
from cowrie.shell.honeypot import HoneyPotCommand
commands = {}
class command_base64(HoneyPotCommand):
"""
author: Ivan Korolev (@fe7ch)
"""
def start(self):
"""
"""
self.mode = 'e'
self.ignore = False
try:
optlist, args = getopt.getopt(self.args, 'diw:', ['version', 'help', 'decode', 'ignore-garbage', 'wrap='])
except getopt.GetoptError as err:
self.errorWrite('Unrecognized option\n')
self.exit()
return
for opt in optlist:
if opt[0] == '--help':
self.write("""Usage: base64 [OPTION]... [FILE]
Base64 encode or decode FILE, or standard input, to standard output.
Mandatory arguments to long options are mandatory for short options too.
-d, --decode decode data
-i, --ignore-garbage when decoding, ignore non-alphabet characters
-w, --wrap=COLS wrap encoded lines after COLS character (default 76).
Use 0 to disable line wrapping
--help display this help and exit
--version output version information and exit
With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base64 alphabet in RFC 3548.
When decoding, the input may contain newlines in addition to the bytes of
the formal base64 alphabet. Use --ignore-garbage to attempt to recover
from any other non-alphabet bytes in the encoded stream.
Report base64 bugs to bug-coreutils@gnu.org
GNU coreutils home page: <http://www.gnu.org/software/coreutils/>
General help using GNU software: <http://www.gnu.org/gethelp/>
For complete documentation, run: info coreutils 'base64 invocation'
""")
self.exit()
return
elif opt[0] == '--version':
self.write("""base64 (GNU coreutils) 8.21
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Simon Josefsson.
""")
self.exit()
return
elif opt[0] == '-d' or opt[0] == '--decode':
self.mode = 'd'
elif opt[0] == '-i' or opt[0] == '--ignore-garbage':
self.ignore = True
elif opt[0] == '-w' or opt[0] == 'wrap':
pass
if self.input_data:
self.dojob(self.input_data)
else:
if len(args) > 1:
self.errorWrite(
"""base64: extra operand '%s'
Try 'base64 --help' for more information.
""" % args[0])
self.exit()
return
pname = self.fs.resolve_path(args[0], self.protocol.cwd)
if not self.fs.isdir(pname):
try:
self.dojob(self.fs.file_contents(pname))
except Exception as e:
print(str(e))
self.errorWrite('base64: {}: No such file or directory\n'.format(args[0]))
else:
self.errorWrite('base64: read error: Is a directory\n')
self.exit()
def dojob(self, s):
if self.ignore:
s = ''.join([i for i in s if i in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='])
if self.mode == 'e':
self.write(s.encode('base64'))
else:
try:
self.write(s.decode('base64'))
except:
self.errorWrite("base64: invalid input\n")
def lineReceived(self, line):
log.msg(eventid='cowrie.session.input',
realm='base64',
input=line,
format='INPUT (%(realm)s): %(input)s')
self.dojob(line)
def handle_CTRL_D(self):
self.exit()
commands['/usr/bin/base64'] = command_base64

View File

@ -11,11 +11,10 @@ from cowrie.core.config import CONFIG
commands = {}
class command_last(HoneyPotCommand):
def call(self):
fn = '%s/lastlog.txt' % CONFIG.get('honeypot', 'log_path')
if not os.path.exists(fn):
return
l = list(self.args)
numlines = 25
while len(l):
@ -26,8 +25,16 @@ class command_last(HoneyPotCommand):
numlines = int(arg[1:])
elif arg == '-n' and len(l) and l[0].isdigit():
numlines = int(l.pop(0))
data = utils.tail(file(fn), numlines)
self.write(''.join(data))
self.write('%-8s %-12s %-16s %s still logged in\n' % \
(self.protocol.user.username, "pts/0", self.protocol.clientIP,
time.strftime('%a %b %d %H:%M', time.localtime(self.protocol.logintime)) ))
self.write("\n")
self.write("wtmp begins %s\n" % \
time.strftime('%a %b %d %H:%M:%S %Y', time.localtime(self.protocol.logintime // (3600*24) * (3600*24) + 63 )) )
commands['/usr/bin/last'] = command_last
# vim: set sw=4 et:

View File

@ -19,6 +19,7 @@ from twisted.python import log, compat
from cowrie.shell.honeypot import HoneyPotCommand
from cowrie.shell.fs import *
from cowrie.core.artifact import Artifact
from cowrie.core.config import CONFIG
"""
@ -120,6 +121,7 @@ class command_wget(HoneyPotCommand):
if not path or not self.fs.exists(path) or not self.fs.isdir(path):
self.errorWrite('wget: %s: Cannot open: No such file or directory\n' % outfile)
self.exit()
return
self.url = url
@ -128,9 +130,8 @@ class command_wget(HoneyPotCommand):
self.limit_size = CONFIG.getint('honeypot', 'download_limit_size')
self.downloadPath = CONFIG.get('honeypot', 'download_path')
self.artifactFile = tempfile.NamedTemporaryFile(dir=self.downloadPath, delete=False)
self.artifactFile = Artifact(outfile)
# HTTPDownloader will close() the file object so need to preserve the name
self.artifactName = self.artifactFile.name
d = self.download(url, outfile, self.artifactFile)
if d:
@ -139,7 +140,6 @@ class command_wget(HoneyPotCommand):
else:
self.exit()
def download(self, url, fakeoutfile, outputfile, *args, **kwargs):
"""
url - URL to download
@ -182,49 +182,42 @@ class command_wget(HoneyPotCommand):
return factory.deferred
def handle_CTRL_C(self):
"""
"""
self.errorWrite('^C\n')
self.connection.transport.loseConnection()
def success(self, data, outfile):
"""
"""
if not os.path.isfile(self.artifactName):
log.msg("there's no file " + self.artifactName)
if not os.path.isfile(self.artifactFile.shasumFilename):
log.msg("there's no file " + self.artifactFile.shasumFilename)
self.exit()
with open(self.artifactName, 'rb') as f:
filedata = f.read()
shasum = hashlib.sha256(filedata).hexdigest()
hash_path = os.path.join(self.downloadPath, shasum)
# Rename temp file to the hash
if not os.path.exists(hash_path):
os.rename(self.artifactName, hash_path)
unique = True
else:
os.remove(self.artifactName)
log.msg("Not storing duplicate content " + shasum)
unique = False
# log to cowrie.log
log.msg(format='Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s',
url=self.url,
outfile=self.artifactFile.shasumFilename,
shasum=self.artifactFile.shasum)
# log to output modules
self.protocol.logDispatch(eventid='cowrie.session.file_download',
format='Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s',
url=self.url, outfile=hash_path, shasum=shasum, unique=unique)
format='Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s',
url=self.url,
outfile=self.artifactFile.shasumFilename,
shasum=self.artifactFile.shasum)
# Update honeyfs to point to downloaded file or write to screen
if outfile != '-':
self.fs.update_realfile(self.fs.getfile(outfile), hash_path)
self.fs.update_realfile(self.fs.getfile(outfile), self.artifactFile.shasumFilename)
self.fs.chown(outfile, self.protocol.user.uid, self.protocol.user.gid)
else:
self.write(filedata)
with open(self.artifactFile.shasumFilename, 'rb') as f:
self.write(f.read())
self.exit()
def error(self, error, url):
"""
"""
@ -233,14 +226,22 @@ class command_wget(HoneyPotCommand):
self.errorWrite(errorMessage + '\n')
# Real wget also adds this:
if hasattr(error, 'webStatus') and hasattr(error, 'webMessage'): # exceptions
self.errorWrite('{} ERROR {}: {}\n'.format(time.strftime('%Y-%m-%d %T'), error.webStatus.decode(), error.webMessage.decode()))
self.errorWrite('{} ERROR {}: {}\n'.format(time.strftime('%Y-%m-%d %T'), error.webStatus.decode(),
error.webMessage.decode()))
else:
self.errorWrite('{} ERROR 404: Not Found.\n'.format(time.strftime('%Y-%m-%d %T')))
self.protocol.logDispatch(eventid='cowrie.session.file_download.failed',
format='Attempt to download file(s) from URL (%(url)s) failed',
url=self.url)
# prevent cowrie from crashing if the terminal have been already destroyed
try:
self.protocol.logDispatch(eventid='cowrie.session.file_download.failed',
format='Attempt to download file(s) from URL (%(url)s) failed',
url=self.url)
except:
pass
self.exit()
commands['/usr/bin/wget'] = command_wget
commands['/usr/bin/dget'] = command_wget
@ -258,7 +259,6 @@ class HTTPProgressDownloader(client.HTTPDownloader):
self.nomore = False
self.quiet = self.wget.quiet
def noPage(self, reason): # Called for non-200 responses
"""
"""
@ -272,7 +272,6 @@ class HTTPProgressDownloader(client.HTTPDownloader):
client.HTTPDownloader.noPage(self, reason)
def gotHeaders(self, headers):
"""
"""
@ -292,9 +291,9 @@ class HTTPProgressDownloader(client.HTTPDownloader):
if self.totallength > 0:
if not self.quiet:
self.wget.errorWrite('Length: %d (%s) [%s]\n' % \
(self.totallength,
sizeof_fmt(self.totallength),
self.contenttype))
(self.totallength,
sizeof_fmt(self.totallength),
self.contenttype))
else:
if not self.quiet:
self.wget.errorWrite('Length: unspecified [{}]\n'.format(self.contenttype))
@ -309,7 +308,6 @@ class HTTPProgressDownloader(client.HTTPDownloader):
return client.HTTPDownloader.gotHeaders(self, headers)
def pagePart(self, data):
"""
"""
@ -348,17 +346,17 @@ class HTTPProgressDownloader(client.HTTPDownloader):
if self.totallength != 0 and self.currentlength != self.totallength:
return client.HTTPDownloader.pageEnd(self)
if not self.quiet:
self.wget.errorWrite('\r100%%[%s] %s %dK/s' % \
('%s>' % (38 * '='),
splitthousands(str(int(self.totallength))).ljust(12),
self.speed / 1000))
self.wget.errorWrite('\r100%%[%s] %s %dK/s' %
('%s>' % (38 * '='),
splitthousands(str(int(self.totallength))).ljust(12),
self.speed / 1000))
self.wget.errorWrite('\n\n')
self.wget.errorWrite(
'%s (%d KB/s) - `%s\' saved [%d/%d]\n\n' % \
'%s (%d KB/s) - `%s\' saved [%d/%d]\n\n' %
(time.strftime('%Y-%m-%d %H:%M:%S'),
self.speed / 1000,
self.fakeoutfile, self.currentlength, self.totallength))
self.wget.fs.mkfile(self.fakeoutfile, 0, 0, self.totallength, 33188)
if self.fakeoutfile != '-':
self.wget.fs.mkfile(self.fakeoutfile, 0, 0, self.totallength, 33188)
return client.HTTPDownloader.pageEnd(self)

View File

@ -24,14 +24,13 @@ from __future__ import division, absolute_import
import hashlib
import os
import re
import time
import tempfile
from twisted.python import log
from cowrie.core.config import CONFIG
class Artifact:
"""
"""
@ -45,6 +44,8 @@ class Artifact:
self.fp = tempfile.NamedTemporaryFile(dir=self.artifactDir, delete=False)
self.tempFilename = self.fp.name
self.shasum = ''
self.shasumFilename = ''
def __enter__(self):
"""
@ -75,26 +76,23 @@ class Artifact:
"""
size = self.fp.tell()
self.fp.seek(0)
shasum = hashlib.sha256(self.fp.read()).hexdigest()
data = self.fp.read()
self.fp.close()
shasumFilename = self.artifactDir + "/" + shasum
self.shasum = hashlib.sha256(data).hexdigest()
self.shasumFilename = os.path.join(self.artifactDir, self.shasum)
if size == 0 and keepEmpty == False:
if size == 0 and not keepEmpty:
log.msg("Not storing empty file")
os.remove(self.fp.name)
elif os.path.exists(shasumFilename):
elif os.path.exists(self.shasumFilename):
log.msg("Not storing duplicate content " + self.shasum)
os.remove(self.fp.name)
else:
os.rename(self.fp.name, shasumFilename)
os.rename(self.fp.name, self.shasumFilename)
umask = os.umask(0)
os.umask(umask)
os.chmod(shasumFilename, 0o666 & ~umask)
os.chmod(self.shasumFilename, 0o666 & ~umask)
# if size>0:
# linkName = self.artifactDir + "/" \
# + time.strftime('%Y%m%dT%H%M%S') \
# + "_" + re.sub('[^-A-Za-z0-9]', '_', self.label)
# os.symlink(shasum, linkName)
return shasum, shasumFilename
return self.shasum, self.shasumFilename

View File

@ -2,7 +2,7 @@
from __future__ import division, absolute_import
import pyes
from elasticsearch import Elasticsearch
import cowrie.core.output
@ -19,7 +19,7 @@ class Output(cowrie.core.output.Output):
"""
self.host = CONFIG.get('output_elasticsearch', 'host')
self.port = CONFIG.get('output_elasticsearch', 'port')
self.index =CONFIGg.get('output_elasticsearch', 'index')
self.index =CONFIG.get('output_elasticsearch', 'index')
self.type = CONFIG.get('output_elasticsearch', 'type')
cowrie.core.output.Output.__init__(self)
@ -27,7 +27,7 @@ class Output(cowrie.core.output.Output):
def start(self):
"""
"""
self.es = pyes.ES('{0}:{1}'.format(self.host, self.port))
self.es = Elasticsearch('{0}:{1}'.format(self.host, self.port))
def stop(self):
@ -44,5 +44,4 @@ class Output(cowrie.core.output.Output):
if i.startswith('log_'):
del logentry[i]
self.es.index(logentry, self.index, self.type)
self.es.index(index=self.index, doc_type=self.type, body=logentry)

View File

@ -158,6 +158,13 @@ class Output(cowrie.core.output.Output):
' VALUES (%s, FROM_UNIXTIME(%s), %s, %s, %s)',
(entry["session"], entry["time"],
entry['url'], entry['outfile'], entry['shasum']))
elif entry["eventid"] == 'cowrie.session.file_download.failed':
self.simpleQuery('INSERT INTO `downloads`' + \
' (`session`, `timestamp`, `url`, `outfile`, `shasum`)' + \
' VALUES (%s, FROM_UNIXTIME(%s), %s, %s, %s)',
(entry["session"], entry["time"],
entry['url'], 'NULL', 'NULL'))
elif entry["eventid"] == 'cowrie.session.file_upload':
self.simpleQuery('INSERT INTO `downloads`' + \

View File

@ -5,6 +5,13 @@ from cowrie.core.config import CONFIG
import redis
import json
from ConfigParser import NoOptionError
SEND_METHODS = {
'lpush': lambda redis_client, key, message: redis_client.lpush(key, message),
'rpush': lambda redis_client, key, message: redis_client.rpush(key, message),
'publish': lambda redis_client, key, message: redis_client.publish(key, message),
}
class Output(cowrie.core.output.Output):
@ -15,11 +22,29 @@ class Output(cowrie.core.output.Output):
"""
Initialize pymisp module and ObjectWrapper (Abstract event and object creation)
"""
self.host = CONFIG.get('output_redis', 'host')
self.port = CONFIG.get('output_redis', 'port')
self.db = CONFIG.get('output_redis', 'db')
host = CONFIG.get('output_redis', 'host')
port = CONFIG.get('output_redis', 'port')
try:
db = CONFIG.get('output_redis', 'db')
except NoOptionError:
db = 0
try:
password = CONFIG.get('output_redis', 'password')
except NoOptionError:
password = None
self.redis = redis.StrictRedis(host=host, port=port, db=db,
password=password)
self.keyname = CONFIG.get('output_redis', 'keyname')
self.redis = redis.StrictRedis(self.host, self.port, self.db)
try:
self.send_method = SEND_METHODS[CONFIG.get('output_redis', 'send_method')]
except (NoOptionError, KeyError):
self.send_method = SEND_METHODS['lpush']
def stop(self):
pass
@ -33,4 +58,4 @@ class Output(cowrie.core.output.Output):
# Remove twisted 15 legacy keys
if i.startswith('log_'):
del logentry[i]
self.redis.lpush(self.keyname, json.dumps(logentry))
self.send_method(self.redis, self.keyname, json.dumps(logentry))

View File

@ -7,6 +7,8 @@ from __future__ import division, absolute_import
import os
from twisted.internet import defer, threads
from twisted.python import log
from botocore.session import get_session
from botocore.exceptions import ClientError
@ -14,6 +16,7 @@ from botocore.exceptions import ClientError
import cowrie.core.output
from cowrie.core.config import CONFIG
from configparser import NoOptionError
@ -23,10 +26,16 @@ class Output(cowrie.core.output.Output):
self.seen = set()
self.session = get_session()
self.session.set_credentials(
CONFIG.get("output_s3", "access_key_id"),
CONFIG.get("output_s3", "secret_access_key"),
)
try:
if CONFIG.get("output_s3", "access_key_id") and CONFIG.get("output_s3", "secret_access_key"):
self.session.set_credentials(
CONFIG.get("output_s3", "access_key_id"),
CONFIG.get("output_s3", "secret_access_key"),
)
except NoOptionError:
log.msg("No AWS credentials found in config - using botocore global settings.")
self.client = self.session.create_client(
's3',
region_name=CONFIG.get("output_s3", "region"),

View File

@ -111,7 +111,7 @@ class Output(cowrie.core.output.Output):
' VALUES (?, ?, ?, ?)',
(entry["session"], entry["timestamp"],
0, entry["input"]))
elif entry["eventid"] == 'cowrie.session.file_download':
self.simpleQuery('INSERT INTO `downloads`' + \
' (`session`, `timestamp`, `url`, `outfile`, `shasum`)' + \
@ -119,6 +119,13 @@ class Output(cowrie.core.output.Output):
(entry["session"], entry["timestamp"],
entry['url'], entry['outfile'], entry['shasum']))
elif entry["eventid"] == 'cowrie.session.file_download.failed':
self.simpleQuery('INSERT INTO `downloads`' + \
' (`session`, `timestamp`, `url`, `outfile`, `shasum`)' + \
' VALUES (?, ?, ?, ?, ?)',
(entry["session"], entry["timestamp"],
entry['url'], 'NULL', 'NULL'))
elif entry["eventid"] == 'cowrie.session.file_download':
self.simpleQuery('INSERT INTO `input`' + \
' (`session`, `timestamp`, `realm`, `input`)' + \

View File

@ -314,24 +314,9 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin
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' % (CONFIG.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

View File

@ -14,7 +14,7 @@ from cowrie.core.config import CONFIG
def cowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar):
"""
This function will redirect an SSH forward request to a another address
This function will redirect an SSH forward request to another address
or will log the request and do nothing
"""
remoteHP, origHP = forwarding.unpackOpen_direct_tcpip(data)
@ -24,6 +24,7 @@ def cowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avata
dst_ip=remoteHP[0], dst_port=remoteHP[1],
src_ip=origHP[0], src_port=origHP[1])
# Forward redirect
try:
if CONFIG.getboolean('ssh', 'forward_redirect') == True:
redirectEnabled = True
@ -50,6 +51,33 @@ def cowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avata
return SSHConnectForwardingChannel(remoteHPNew,
remoteWindow=remoteWindow, remoteMaxPacket=remoteMaxPacket)
# TCP tunnel
try:
if CONFIG.getboolean('ssh', 'forward_tunnel') == True:
tunnelEnabled = True
else:
tunnelEnabled = False
except:
tunnelEnabled = False
if tunnelEnabled:
tunnels = {}
items = CONFIG.items('ssh')
for i in items:
if i[0].startswith('forward_tunnel_'):
destPort = i[0].split('_')[-1]
tunnelHP = i[1].split(':')
tunnels[int(destPort)] = (tunnelHP[0], int(tunnelHP[1]))
if remoteHP[1] in tunnels:
remoteHPNew = tunnels[remoteHP[1]]
log.msg(eventid='cowrie.direct-tcpip.tunnel',
format='tunneled direct-tcp connection request %(src_ip)s:%(src_port)d->%(dst_ip)s:%(dst_port)d to %(new_ip)s:%(new_port)d',
new_ip=remoteHPNew[0], new_port=remoteHPNew[1],
dst_ip=remoteHP[0], dst_port=remoteHP[1],
src_ip=origHP[0], src_port=origHP[1])
return TCPTunnelForwardingChannel(remoteHPNew, remoteHP,
remoteWindow=remoteWindow, remoteMaxPacket=remoteMaxPacket)
return FakeForwardingChannel(remoteHP,
remoteWindow=remoteWindow, remoteMaxPacket=remoteMaxPacket)
@ -87,3 +115,64 @@ class FakeForwardingChannel(forwarding.SSHConnectForwardingChannel):
dst_ip=self.hostport[0], dst_port=self.hostport[1], data=repr(data))
self._close("Connection refused")
class TCPTunnelForwardingChannel(forwarding.SSHConnectForwardingChannel):
"""
This class modifies the original to perform TCP tunneling via the CONNECT method
"""
name = b'cowrie-tunneled-direct-tcpip'
def __init__(self, hostport, dstport, *args, **kw):
"""
Modifies the original to store where the data was originally going to go
"""
forwarding.SSHConnectForwardingChannel.__init__(self, hostport, *args, **kw)
self.dstport = dstport
self.tunnel_established = False
def channelOpen(self, specificData):
"""
Modifies the original to send a TCP tunnel request via the CONNECT method
"""
forwarding.SSHConnectForwardingChannel.channelOpen(self, specificData)
dst = self.dstport[0] + b':' + str(self.dstport[1])
connect_hdr = b'CONNECT ' + dst + b" HTTP/1.1\r\n\r\n"
forwarding.SSHConnectForwardingChannel.dataReceived(self, connect_hdr)
def dataReceived(self, data):
"""
"""
log.msg(eventid='cowrie.tunnelproxy-tcpip.data',
format='sending via tunnel proxy %(data)s', data=repr(data))
forwarding.SSHConnectForwardingChannel.dataReceived(self, data)
def write(self, data):
"""
Modifies the original to stip off the TCP tunnel response
"""
if not self.tunnel_established and data[:4].lower() == b'http':
# Check proxy response code
try:
res_code = int(data.split(' ')[1], 10)
except ValueError:
log.err('Failed to parse TCP tunnel response code')
self._close("Connection refused")
if res_code != 200:
log.err('Unexpected response code: {}'.format(res_code))
self._close("Connection refused")
# Strip off rest of packet
eop = data.find("\r\n\r\n")
if eop > -1:
data = data[eop + 4:]
# This only happens once when the channel is opened
self.tunnel_established = True
forwarding.SSHConnectForwardingChannel.write(self, data)
def eofReceived(self):
self.loseConnection()

View File

@ -56,7 +56,7 @@ CREATE TABLE IF NOT EXISTS `downloads` (
`session` CHAR( 32 ) NOT NULL,
`timestamp` datetime NOT NULL,
`url` text NOT NULL,
`outfile` text NOT NULL,
`outfile` text default NULL,
`shasum` varchar(64) default NULL,
PRIMARY KEY (`id`),
KEY `session` (`session`,`timestamp`)

View File

@ -50,7 +50,7 @@ CREATE TABLE IF NOT EXISTS `downloads` (
`session` CHAR( 32 ) NOT NULL,
`timestamp` datetime NOT NULL,
`url` text NOT NULL,
`outfile` text NOT NULL,
`outfile` text default NULL,
`shasum` varchar(64) default NULL
) ;
CREATE INDEX downloads_index ON downloads(session, timestamp);

1
doc/sql/update12.sql Normal file
View File

@ -0,0 +1 @@
ALTER TABLE `downloads` MODIFY `outfile` text default NULL;

40
doc/squid/README.md Normal file
View File

@ -0,0 +1,40 @@
# Using TCP tunneling with Squid
## Prerequisites
* Working Cowrie installation
* Working Squid installation with CONNECT allowed
* (optional) Rate limit and black/white lists in Squid
## Installation
```
$ sudo apt-get install squid
```
## Squid Configuration
See `squid.conf` for an example configuration.
## Cowrie Configuration
Uncomment and update the following entries to ~/cowrie/cowrie.cfg under the SSH section:
```
forward_tunnel = true
forward_tunnel_80 = 127.0.0.1:3128
forward_tunnel_443 = 127.0.0.1:3128
```
## Restart Cowrie
```
$ cd ~/cowrie/bin/
$ ./cowrie restart
```

19
doc/squid/squid.conf Normal file
View File

@ -0,0 +1,19 @@
## Allow HTTP(S) (Ports 80, 443)
acl Safe_ports port 80 # http
acl Safe_ports port 443 # https
## Allow TCP tunneling via the CONNECT method
acl CONNECT method CONNECT
## Restrict bandwidth to ~128 kbps
delay_pools 1
delay_class 1 1
delay_parameters 1 16000/128000
delay_access 1 allow localhost
## Only allow localhost to tunnel Safe_ports
http_access deny CONNECT !Safe_ports
http_access allow localhost
http_access deny all
## Bind Squid to localhost
http_port 127.0.0.1:3128

View File

@ -5,7 +5,7 @@ csirtgsdk>=0.0.0a17 # Specify version because pip won't install pre-release vers
requests
# elasticsearch
pyes
elasticsearch
# mysql
# If this fails, see documentation /home/cowrie/cowrie/doc/sql/README.md