mirror of
https://github.com/cowrie/cowrie.git
synced 2025-07-01 18:07:27 -04:00
Merge branch 'master' of http://www.github.com/micheloosterhof/cowrie
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,4 +15,5 @@ __pycache__/
|
||||
twisted/plugins/dropin.cache
|
||||
.DS_Store
|
||||
_trial_temp
|
||||
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
@ -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
|
||||
|
||||
@ -5,6 +5,7 @@ __all__ = [
|
||||
'adduser',
|
||||
'apt',
|
||||
'base',
|
||||
'base64',
|
||||
'busybox',
|
||||
'curl',
|
||||
'dd',
|
||||
|
||||
@ -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
126
cowrie/commands/base64.py
Normal 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
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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`' + \
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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`)' + \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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`)
|
||||
|
||||
@ -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
1
doc/sql/update12.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE `downloads` MODIFY `outfile` text default NULL;
|
||||
40
doc/squid/README.md
Normal file
40
doc/squid/README.md
Normal 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
19
doc/squid/squid.conf
Normal 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
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user