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 twisted/plugins/dropin.cache
.DS_Store .DS_Store
_trial_temp _trial_temp
.project
.pydevproject

View File

@ -286,6 +286,19 @@ forward_redirect = false
# forward_redirect_587 = 127.0.0.1:12525 # 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 # Telnet Specific Options
@ -531,7 +544,9 @@ logfile = log/cowrie.json
# #
#[output_s3] #[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 #access_key_id = AKIDEXAMPLE
#secret_access_key = wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY #secret_access_key = wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY
# #
@ -558,7 +573,21 @@ logfile = log/cowrie.json
#[output_redis] #[output_redis]
# Host of the redis server. Required
#host = 127.0.0.1 #host = 127.0.0.1
# Port of the redis server. Required
#port = 6379 #port = 6379
# DB of the redis server. Defaults to 0
#db = 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 #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', 'adduser',
'apt', 'apt',
'base', 'base',
'base64',
'busybox', 'busybox',
'curl', 'curl',
'dd', 'dd',

View File

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

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 = {} commands = {}
class command_last(HoneyPotCommand): class command_last(HoneyPotCommand):
def call(self): def call(self):
fn = '%s/lastlog.txt' % CONFIG.get('honeypot', 'log_path')
if not os.path.exists(fn):
return
l = list(self.args) l = list(self.args)
numlines = 25 numlines = 25
while len(l): while len(l):
@ -26,8 +25,16 @@ class command_last(HoneyPotCommand):
numlines = int(arg[1:]) numlines = int(arg[1:])
elif arg == '-n' and len(l) and l[0].isdigit(): elif arg == '-n' and len(l) and l[0].isdigit():
numlines = int(l.pop(0)) 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 commands['/usr/bin/last'] = command_last
# vim: set sw=4 et: # 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.honeypot import HoneyPotCommand
from cowrie.shell.fs import * from cowrie.shell.fs import *
from cowrie.core.artifact import Artifact
from cowrie.core.config import CONFIG 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): 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.errorWrite('wget: %s: Cannot open: No such file or directory\n' % outfile)
self.exit() self.exit()
return
self.url = url self.url = url
@ -128,9 +130,8 @@ class command_wget(HoneyPotCommand):
self.limit_size = CONFIG.getint('honeypot', 'download_limit_size') self.limit_size = CONFIG.getint('honeypot', 'download_limit_size')
self.downloadPath = CONFIG.get('honeypot', 'download_path') 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 # HTTPDownloader will close() the file object so need to preserve the name
self.artifactName = self.artifactFile.name
d = self.download(url, outfile, self.artifactFile) d = self.download(url, outfile, self.artifactFile)
if d: if d:
@ -139,7 +140,6 @@ class command_wget(HoneyPotCommand):
else: else:
self.exit() self.exit()
def download(self, url, fakeoutfile, outputfile, *args, **kwargs): def download(self, url, fakeoutfile, outputfile, *args, **kwargs):
""" """
url - URL to download url - URL to download
@ -182,49 +182,42 @@ class command_wget(HoneyPotCommand):
return factory.deferred return factory.deferred
def handle_CTRL_C(self): def handle_CTRL_C(self):
""" """
""" """
self.errorWrite('^C\n') self.errorWrite('^C\n')
self.connection.transport.loseConnection() self.connection.transport.loseConnection()
def success(self, data, outfile): def success(self, data, outfile):
""" """
""" """
if not os.path.isfile(self.artifactName): if not os.path.isfile(self.artifactFile.shasumFilename):
log.msg("there's no file " + self.artifactName) log.msg("there's no file " + self.artifactFile.shasumFilename)
self.exit() self.exit()
with open(self.artifactName, 'rb') as f: # log to cowrie.log
filedata = f.read() log.msg(format='Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s',
shasum = hashlib.sha256(filedata).hexdigest() url=self.url,
hash_path = os.path.join(self.downloadPath, shasum) outfile=self.artifactFile.shasumFilename,
shasum=self.artifactFile.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 output modules
self.protocol.logDispatch(eventid='cowrie.session.file_download', self.protocol.logDispatch(eventid='cowrie.session.file_download',
format='Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s', format='Downloaded URL (%(url)s) with SHA-256 %(shasum)s to %(outfile)s',
url=self.url, outfile=hash_path, shasum=shasum, unique=unique) url=self.url,
outfile=self.artifactFile.shasumFilename,
shasum=self.artifactFile.shasum)
# Update honeyfs to point to downloaded file or write to screen # Update honeyfs to point to downloaded file or write to screen
if outfile != '-': 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) self.fs.chown(outfile, self.protocol.user.uid, self.protocol.user.gid)
else: else:
self.write(filedata) with open(self.artifactFile.shasumFilename, 'rb') as f:
self.write(f.read())
self.exit() self.exit()
def error(self, error, url): def error(self, error, url):
""" """
""" """
@ -233,14 +226,22 @@ class command_wget(HoneyPotCommand):
self.errorWrite(errorMessage + '\n') self.errorWrite(errorMessage + '\n')
# Real wget also adds this: # Real wget also adds this:
if hasattr(error, 'webStatus') and hasattr(error, 'webMessage'): # exceptions 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: else:
self.errorWrite('{} ERROR 404: Not Found.\n'.format(time.strftime('%Y-%m-%d %T'))) self.errorWrite('{} ERROR 404: Not Found.\n'.format(time.strftime('%Y-%m-%d %T')))
# prevent cowrie from crashing if the terminal have been already destroyed
try:
self.protocol.logDispatch(eventid='cowrie.session.file_download.failed', self.protocol.logDispatch(eventid='cowrie.session.file_download.failed',
format='Attempt to download file(s) from URL (%(url)s) failed', format='Attempt to download file(s) from URL (%(url)s) failed',
url=self.url) url=self.url)
except:
pass
self.exit() self.exit()
commands['/usr/bin/wget'] = command_wget commands['/usr/bin/wget'] = command_wget
commands['/usr/bin/dget'] = command_wget commands['/usr/bin/dget'] = command_wget
@ -258,7 +259,6 @@ class HTTPProgressDownloader(client.HTTPDownloader):
self.nomore = False self.nomore = False
self.quiet = self.wget.quiet self.quiet = self.wget.quiet
def noPage(self, reason): # Called for non-200 responses def noPage(self, reason): # Called for non-200 responses
""" """
""" """
@ -272,7 +272,6 @@ class HTTPProgressDownloader(client.HTTPDownloader):
client.HTTPDownloader.noPage(self, reason) client.HTTPDownloader.noPage(self, reason)
def gotHeaders(self, headers): def gotHeaders(self, headers):
""" """
""" """
@ -309,7 +308,6 @@ class HTTPProgressDownloader(client.HTTPDownloader):
return client.HTTPDownloader.gotHeaders(self, headers) return client.HTTPDownloader.gotHeaders(self, headers)
def pagePart(self, data): def pagePart(self, data):
""" """
""" """
@ -348,17 +346,17 @@ class HTTPProgressDownloader(client.HTTPDownloader):
if self.totallength != 0 and self.currentlength != self.totallength: if self.totallength != 0 and self.currentlength != self.totallength:
return client.HTTPDownloader.pageEnd(self) return client.HTTPDownloader.pageEnd(self)
if not self.quiet: if not self.quiet:
self.wget.errorWrite('\r100%%[%s] %s %dK/s' % \ self.wget.errorWrite('\r100%%[%s] %s %dK/s' %
('%s>' % (38 * '='), ('%s>' % (38 * '='),
splitthousands(str(int(self.totallength))).ljust(12), splitthousands(str(int(self.totallength))).ljust(12),
self.speed / 1000)) self.speed / 1000))
self.wget.errorWrite('\n\n') self.wget.errorWrite('\n\n')
self.wget.errorWrite( 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'), (time.strftime('%Y-%m-%d %H:%M:%S'),
self.speed / 1000, self.speed / 1000,
self.fakeoutfile, self.currentlength, self.totallength)) self.fakeoutfile, self.currentlength, self.totallength))
if self.fakeoutfile != '-':
self.wget.fs.mkfile(self.fakeoutfile, 0, 0, self.totallength, 33188) self.wget.fs.mkfile(self.fakeoutfile, 0, 0, self.totallength, 33188)
return client.HTTPDownloader.pageEnd(self) return client.HTTPDownloader.pageEnd(self)

View File

@ -24,14 +24,13 @@ from __future__ import division, absolute_import
import hashlib import hashlib
import os import os
import re
import time
import tempfile import tempfile
from twisted.python import log from twisted.python import log
from cowrie.core.config import CONFIG from cowrie.core.config import CONFIG
class Artifact: class Artifact:
""" """
""" """
@ -45,6 +44,8 @@ class Artifact:
self.fp = tempfile.NamedTemporaryFile(dir=self.artifactDir, delete=False) self.fp = tempfile.NamedTemporaryFile(dir=self.artifactDir, delete=False)
self.tempFilename = self.fp.name self.tempFilename = self.fp.name
self.shasum = ''
self.shasumFilename = ''
def __enter__(self): def __enter__(self):
""" """
@ -75,26 +76,23 @@ class Artifact:
""" """
size = self.fp.tell() size = self.fp.tell()
self.fp.seek(0) self.fp.seek(0)
shasum = hashlib.sha256(self.fp.read()).hexdigest() data = self.fp.read()
self.fp.close() 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) 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) os.remove(self.fp.name)
else: else:
os.rename(self.fp.name, shasumFilename) os.rename(self.fp.name, self.shasumFilename)
umask = os.umask(0) umask = os.umask(0)
os.umask(umask) os.umask(umask)
os.chmod(shasumFilename, 0o666 & ~umask) os.chmod(self.shasumFilename, 0o666 & ~umask)
# if size>0: return self.shasum, self.shasumFilename
# 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

View File

@ -2,7 +2,7 @@
from __future__ import division, absolute_import from __future__ import division, absolute_import
import pyes from elasticsearch import Elasticsearch
import cowrie.core.output import cowrie.core.output
@ -19,7 +19,7 @@ class Output(cowrie.core.output.Output):
""" """
self.host = CONFIG.get('output_elasticsearch', 'host') self.host = CONFIG.get('output_elasticsearch', 'host')
self.port = CONFIG.get('output_elasticsearch', 'port') 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') self.type = CONFIG.get('output_elasticsearch', 'type')
cowrie.core.output.Output.__init__(self) cowrie.core.output.Output.__init__(self)
@ -27,7 +27,7 @@ class Output(cowrie.core.output.Output):
def start(self): 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): def stop(self):
@ -44,5 +44,4 @@ class Output(cowrie.core.output.Output):
if i.startswith('log_'): if i.startswith('log_'):
del logentry[i] 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

@ -159,6 +159,13 @@ class Output(cowrie.core.output.Output):
(entry["session"], entry["time"], (entry["session"], entry["time"],
entry['url'], entry['outfile'], entry['shasum'])) 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': elif entry["eventid"] == 'cowrie.session.file_upload':
self.simpleQuery('INSERT INTO `downloads`' + \ self.simpleQuery('INSERT INTO `downloads`' + \
' (`session`, `timestamp`, `url`, `outfile`, `shasum`)' + \ ' (`session`, `timestamp`, `url`, `outfile`, `shasum`)' + \

View File

@ -5,6 +5,13 @@ from cowrie.core.config import CONFIG
import redis import redis
import json 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): 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) Initialize pymisp module and ObjectWrapper (Abstract event and object creation)
""" """
self.host = CONFIG.get('output_redis', 'host') host = CONFIG.get('output_redis', 'host')
self.port = CONFIG.get('output_redis', 'port') port = CONFIG.get('output_redis', 'port')
self.db = CONFIG.get('output_redis', 'db')
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.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): def stop(self):
pass pass
@ -33,4 +58,4 @@ class Output(cowrie.core.output.Output):
# Remove twisted 15 legacy keys # Remove twisted 15 legacy keys
if i.startswith('log_'): if i.startswith('log_'):
del logentry[i] 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 import os
from twisted.internet import defer, threads from twisted.internet import defer, threads
from twisted.python import log
from botocore.session import get_session from botocore.session import get_session
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
@ -14,6 +16,7 @@ from botocore.exceptions import ClientError
import cowrie.core.output import cowrie.core.output
from cowrie.core.config import CONFIG from cowrie.core.config import CONFIG
from configparser import NoOptionError
@ -23,10 +26,16 @@ class Output(cowrie.core.output.Output):
self.seen = set() self.seen = set()
self.session = get_session() self.session = get_session()
try:
if CONFIG.get("output_s3", "access_key_id") and CONFIG.get("output_s3", "secret_access_key"):
self.session.set_credentials( self.session.set_credentials(
CONFIG.get("output_s3", "access_key_id"), CONFIG.get("output_s3", "access_key_id"),
CONFIG.get("output_s3", "secret_access_key"), 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( self.client = self.session.create_client(
's3', 's3',
region_name=CONFIG.get("output_s3", "region"), region_name=CONFIG.get("output_s3", "region"),

View File

@ -119,6 +119,13 @@ class Output(cowrie.core.output.Output):
(entry["session"], entry["timestamp"], (entry["session"], entry["timestamp"],
entry['url'], entry['outfile'], entry['shasum'])) 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': elif entry["eventid"] == 'cowrie.session.file_download':
self.simpleQuery('INSERT INTO `input`' + \ self.simpleQuery('INSERT INTO `input`' + \
' (`session`, `timestamp`, `realm`, `input`)' + \ ' (`session`, `timestamp`, `realm`, `input`)' + \

View File

@ -314,24 +314,9 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin
HoneyPotBaseProtocol.timeoutConnection(self) 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): def connectionLost(self, reason):
""" """
""" """
self.lastlogExit()
HoneyPotBaseProtocol.connectionLost(self, reason) HoneyPotBaseProtocol.connectionLost(self, reason)
recvline.HistoricRecvLine.connectionLost(self, reason) recvline.HistoricRecvLine.connectionLost(self, reason)
self.keyHandlers = None self.keyHandlers = None

View File

@ -14,7 +14,7 @@ from cowrie.core.config import CONFIG
def cowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avatar): 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 or will log the request and do nothing
""" """
remoteHP, origHP = forwarding.unpackOpen_direct_tcpip(data) 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], dst_ip=remoteHP[0], dst_port=remoteHP[1],
src_ip=origHP[0], src_port=origHP[1]) src_ip=origHP[0], src_port=origHP[1])
# Forward redirect
try: try:
if CONFIG.getboolean('ssh', 'forward_redirect') == True: if CONFIG.getboolean('ssh', 'forward_redirect') == True:
redirectEnabled = True redirectEnabled = True
@ -50,6 +51,33 @@ def cowrieOpenConnectForwardingClient(remoteWindow, remoteMaxPacket, data, avata
return SSHConnectForwardingChannel(remoteHPNew, return SSHConnectForwardingChannel(remoteHPNew,
remoteWindow=remoteWindow, remoteMaxPacket=remoteMaxPacket) 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, return FakeForwardingChannel(remoteHP,
remoteWindow=remoteWindow, remoteMaxPacket=remoteMaxPacket) 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)) dst_ip=self.hostport[0], dst_port=self.hostport[1], data=repr(data))
self._close("Connection refused") 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, `session` CHAR( 32 ) NOT NULL,
`timestamp` datetime NOT NULL, `timestamp` datetime NOT NULL,
`url` text NOT NULL, `url` text NOT NULL,
`outfile` text NOT NULL, `outfile` text default NULL,
`shasum` varchar(64) default NULL, `shasum` varchar(64) default NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `session` (`session`,`timestamp`) KEY `session` (`session`,`timestamp`)

View File

@ -50,7 +50,7 @@ CREATE TABLE IF NOT EXISTS `downloads` (
`session` CHAR( 32 ) NOT NULL, `session` CHAR( 32 ) NOT NULL,
`timestamp` datetime NOT NULL, `timestamp` datetime NOT NULL,
`url` text NOT NULL, `url` text NOT NULL,
`outfile` text NOT NULL, `outfile` text default NULL,
`shasum` varchar(64) default NULL `shasum` varchar(64) default NULL
) ; ) ;
CREATE INDEX downloads_index ON downloads(session, timestamp); 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 requests
# elasticsearch # elasticsearch
pyes elasticsearch
# mysql # mysql
# If this fails, see documentation /home/cowrie/cowrie/doc/sql/README.md # If this fails, see documentation /home/cowrie/cowrie/doc/sql/README.md