This commit is contained in:
Michel Oosterhof
2017-02-07 05:09:12 +00:00
23 changed files with 457 additions and 802 deletions

1
.gitignore vendored
View File

@@ -13,4 +13,5 @@ __pycache__/
*env/
twisted/plugins/dropin.cache
.DS_Store
_trial_temp

View File

@@ -35,14 +35,9 @@ Additional functionality over standard kippo:
Software required:
* Python 2.7+, (Python 3 not yet supported due to Twisted dependencies)
* Zope Interface 3.6.0+
* Twisted 12.0+
* python-crypto
* python-cryptography
* python-pyasn1
* python-gmpy2 (recommended)
* python-mysqldb (for MySQL output)
* python-OpenSSL
* python-virtualenv
For Python dependencies, see requirements.txt
## Files of interest:

View File

@@ -385,6 +385,14 @@ logfile = log/cowrie.json
#[output_sqlite]
#db_file = cowrie.db
# MongoDB logging module
#
# MongoDB logging requires an extra Python module: pip install pymongo
#
#[output_mongodb]
#connection_string = mongodb://username:password@host:port/database
#database = dbname
# Splunk SDK output module - Legacy. Requires Splunk API installed
# This sends logs directly to Splunk using the Python REST SDK

View File

@@ -92,10 +92,14 @@ class command_curl(HoneyPotCommand):
self.download_path = cfg.get('honeypot', 'download_path')
self.safeoutfile = '%s/%s_%s' % \
(self.download_path,
time.strftime('%Y%m%d%H%M%S'),
re.sub('[^A-Za-z0-9]', '_', url))
if not hasattr(self, 'safeoutfile'):
tmp_fname = '%s_%s_%s_%s' % \
(time.strftime('%Y%m%d%H%M%S'),
self.protocol.getProtoTransport().transportId,
self.protocol.terminal.transport.session.id,
re.sub('[^A-Za-z0-9]', '_', url))
self.safeoutfile = os.path.join(self.download_path, tmp_fname)
self.deferred = self.download(url, outfile, self.safeoutfile)
if self.deferred:
self.deferred.addCallback(self.success, outfile)
@@ -311,7 +315,7 @@ Options: (H) means HTTP/HTTPS only, (F) means FTP only
self.exit()
shasum = hashlib.sha256(open(self.safeoutfile, 'rb').read()).hexdigest()
hashPath = '%s/%s' % (self.download_path, shasum)
hashPath = os.path.join(self.download_path, shasum)
# If we have content already, delete temp file
if not os.path.exists(hashPath):
@@ -336,7 +340,7 @@ Options: (H) means HTTP/HTTPS only, (F) means FTP only
os.symlink(shasum, self.safeoutfile)
# FIXME: is this necessary?
self.safeoutfile = hashPath
# self.safeoutfile = hashPath
# Update the honeyfs to point to downloaded file
if outfile is not None:

View File

@@ -124,9 +124,8 @@ class command_grep(HoneyPotCommand):
commands['/bin/grep'] = command_grep
commands['/usr/bin/grep'] = command_grep
commands['/usr/bin/egrep'] = command_grep
commands['/usr/bin/fgrep'] = command_grep
commands['/bin/egrep'] = command_grep
commands['/bin/fgrep'] = command_grep
class command_tail(HoneyPotCommand):

View File

@@ -93,10 +93,13 @@ Download a file via FTP
cfg = self.protocol.cfg
url = 'ftp://%s/%s' % (self.host, self.remote_path)
self.download_path = cfg.get('honeypot', 'download_path')
self.safeoutfile = '%s/%s_%s' % \
(self.download_path,
time.strftime('%Y%m%d%H%M%S'),
re.sub('[^A-Za-z0-9]', '_', url))
tmp_fname = '%s_%s_%s_%s' % \
(time.strftime('%Y%m%d%H%M%S'),
self.protocol.getProtoTransport().transportId,
self.protocol.terminal.transport.session.id,
re.sub('[^A-Za-z0-9]', '_', url))
self.safeoutfile = os.path.join(self.download_path, tmp_fname)
result = self.ftp_download(self.safeoutfile)
@@ -110,7 +113,7 @@ Download a file via FTP
return
shasum = hashlib.sha256(open(self.safeoutfile, 'rb').read()).hexdigest()
hash_path = '%s/%s' % (self.download_path, shasum)
hash_path = os.path.join(self.download_path, shasum)
# If we have content already, delete temp file
if not os.path.exists(hash_path):

View File

@@ -4,6 +4,7 @@ import time
import re
import getopt
import random
import os
from twisted.internet import reactor
@@ -164,10 +165,12 @@ gcc version %s (Debian %s-5)""" % (version, version_short, version_short, versio
def generate_file(self, outfile):
data = ""
# TODO: make sure it is written to temp file, not downloads
safeoutfile = '%s/%s_%s' % \
(self.protocol.cfg.get('honeypot', 'download_path'),
time.strftime('%Y%m%d%H%M%S'),
re.sub('[^A-Za-z0-9]', '_', outfile))
tmp_fname = '%s_%s_%s_%s' % \
(time.strftime('%Y%m%d%H%M%S'),
self.protocol.getProtoTransport().transportId,
self.protocol.terminal.transport.session.id,
re.sub('[^A-Za-z0-9]', '_', outfile))
safeoutfile = os.path.join(self.protocol.cfg.get('honeypot', 'download_path'), tmp_fname)
# Data contains random garbage from an actual file, so when
# catting the file, you'll see some 'real' compiled data

View File

@@ -56,10 +56,12 @@ class command_tftp(HoneyPotCommand):
self.download_path = cfg.get('honeypot', 'download_path')
self.safeoutfile = '%s/%s_%s' % \
(self.download_path,
time.strftime('%Y%m%d%H%M%S'),
re.sub('[^A-Za-z0-9]', '_', self.file_to_get))
tmp_fname = '%s_%s_%s_%s' % \
(time.strftime('%Y%m%d%H%M%S'),
self.protocol.getProtoTransport().transportId,
self.protocol.terminal.transport.session.id,
re.sub('[^A-Za-z0-9]', '_', self.file_to_get))
self.safeoutfile = os.path.join(self.download_path, tmp_fname)
try:
tclient.download(self.file_to_get, self.safeoutfile, progresshook)
@@ -67,38 +69,41 @@ class command_tftp(HoneyPotCommand):
self.fs.mkfile(self.file_to_get, 0, 0, tclient.context.metrics.bytes, 33188)
self.fs.update_realfile(self.fs.getfile(self.file_to_get), self.safeoutfile)
shasum = hashlib.sha256(open(self.safeoutfile, 'rb').read()).hexdigest()
hash_path = '%s/%s' % (self.download_path, shasum)
if os.path.exists(self.safeoutfile):
# If we have content already, delete temp file
if not os.path.exists(hash_path):
os.rename(self.safeoutfile, hash_path)
else:
os.remove(self.safeoutfile)
log.msg("Not storing duplicate content " + shasum)
if os.path.getsize(self.safeoutfile) == 0:
os.remove(self.safeoutfile)
self.safeoutfile = None
return
log.msg(eventid='cowrie.session.file_download',
format='Downloaded tftpFile (%(url)s) with SHA-256 %(shasum)s to %(outfile)s',
url=self.file_to_get,
outfile=hash_path,
shasum=shasum)
with open(self.safeoutfile, 'rb') as f:
shasum = hashlib.sha256(f.read()).hexdigest()
hash_path = os.path.join(self.download_path, shasum)
# Link friendly name to hash
os.symlink(shasum, self.safeoutfile)
# If we have content already, delete temp file
if not os.path.exists(hash_path):
os.rename(self.safeoutfile, hash_path)
else:
os.remove(self.safeoutfile)
log.msg("Not storing duplicate content " + shasum)
# FIXME: is this necessary?
self.safeoutfile = hash_path
log.msg(eventid='cowrie.session.file_download',
format='Downloaded tftpFile (%(url)s) with SHA-256 %(shasum)s to %(outfile)s',
url=self.file_to_get,
outfile=hash_path,
shasum=shasum)
# Update the honeyfs to point to downloaded file
f = self.fs.getfile(self.file_to_get)
f[A_REALFILE] = hash_path
# Link friendly name to hash
os.symlink(shasum, self.safeoutfile)
log.msg(eventid='cowrie.session.file_download',
format='Downloaded tftpFile to %(outfile)s',
outfile=self.safeoutfile
)
# Update the honeyfs to point to downloaded file
f = self.fs.getfile(self.file_to_get)
f[A_REALFILE] = hash_path
except tftpy.TftpException, err:
if os.path.exists(self.safeoutfile):
if os.path.getsize(self.safeoutfile) == 0:
os.remove(self.safeoutfile)
return
except KeyboardInterrupt:

View File

@@ -121,10 +121,14 @@ class command_wget(HoneyPotCommand):
self.download_path = cfg.get('honeypot', 'download_path')
self.safeoutfile = '%s/%s_%s' % \
(self.download_path,
time.strftime('%Y%m%d%H%M%S'),
re.sub('[^A-Za-z0-9]', '_', url))
if not hasattr(self, 'safeoutfile'):
tmp_fname = '%s_%s_%s_%s' % \
(time.strftime('%Y%m%d%H%M%S'),
self.protocol.getProtoTransport().transportId,
self.protocol.terminal.transport.session.id,
re.sub('[^A-Za-z0-9]', '_', url))
self.safeoutfile = os.path.join(self.download_path, tmp_fname)
self.deferred = self.download(url, outfile, self.safeoutfile)
if self.deferred:
self.deferred.addCallback(self.success, outfile)
@@ -142,6 +146,9 @@ class command_wget(HoneyPotCommand):
path = parsed.path or '/'
if scheme != 'http' and scheme != 'https':
raise NotImplementedError
if not host:
self.exit()
return None
except:
self.write('%s: Unsupported scheme.\n' % (url,))
self.exit()
@@ -185,8 +192,9 @@ class command_wget(HoneyPotCommand):
log.msg("there's no file " + self.safeoutfile)
self.exit()
shasum = hashlib.sha256(open(self.safeoutfile, 'rb').read()).hexdigest()
hash_path = '%s/%s' % (self.download_path, shasum)
with open(self.safeoutfile, 'rb') as f:
shasum = hashlib.sha256(f.read()).hexdigest()
hash_path = os.path.join(self.download_path, shasum)
# If we have content already, delete temp file
if not os.path.exists(hash_path):
@@ -208,10 +216,10 @@ class command_wget(HoneyPotCommand):
shasum=shasum)
# Link friendly name to hash
os.symlink( shasum, self.safeoutfile )
os.symlink(shasum, self.safeoutfile)
# FIXME: is this necessary?
self.safeoutfile = hash_path
# self.safeoutfile = hash_path
# Update the honeyfs to point to downloaded file
f = self.fs.getfile(outfile)

View File

@@ -32,22 +32,40 @@ class HoneyPotCommand(object):
self.errorWrite = self.protocol.pp.errReceived
# MS-DOS style redirect handling, inside the command
# TODO: handle >>, 2>, etc
if '>' in self.args:
if '>' in self.args or '>>' in self.args:
self.writtenBytes = 0
self.write = self.write_to_file
index = self.args.index(">")
if '>>' in self.args:
index = self.args.index('>>')
b_append = True
else:
index = self.args.index('>')
b_append = False
self.outfile = self.fs.resolve_path(str(self.args[(index + 1)]), self.protocol.cwd)
del self.args[index:]
self.safeoutfile = '%s/%s-%s-%s-redir_%s' % (
self.protocol.cfg.get('honeypot', 'download_path'),
time.strftime('%Y%m%d-%H%M%S'),
self.protocol.getProtoTransport().transportId,
self.protocol.terminal.transport.session.id,
re.sub('[^A-Za-z0-9]', '_', self.outfile))
perm = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
self.fs.mkfile(self.outfile, 0, 0, 0, stat.S_IFREG | perm)
with open(self.safeoutfile, 'a'):
self.fs.update_realfile(self.fs.getfile(self.outfile), self.safeoutfile)
p = self.fs.getfile(self.outfile)
if not p or not p[fs.A_REALFILE] or p[fs.A_REALFILE].startswith('honeyfs') or not b_append:
tmp_fname = '%s-%s-%s-redir_%s' % \
(time.strftime('%Y%m%d-%H%M%S'),
self.protocol.getProtoTransport().transportId,
self.protocol.terminal.transport.session.id,
re.sub('[^A-Za-z0-9]', '_', self.outfile))
self.safeoutfile = os.path.join(self.protocol.cfg.get('honeypot', 'download_path'), tmp_fname)
perm = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
try:
self.fs.mkfile(self.outfile, 0, 0, 0, stat.S_IFREG | perm)
except fs.FileNotFound:
# The outfile locates at a non-existing directory.
self.protocol.pp.outReceived('-bash: %s: No such file or directory\n' % self.outfile)
self.write = self.write_to_failed
self.outfile = None
self.safeoutfile = None
else:
with open(self.safeoutfile, 'a'):
self.fs.update_realfile(self.fs.getfile(self.outfile), self.safeoutfile)
else:
self.safeoutfile = p[fs.A_REALFILE]
def check_arguments(self, application, args):
@@ -78,10 +96,16 @@ class HoneyPotCommand(object):
self.fs.update_size(self.outfile, self.writtenBytes)
def write_to_failed(self, data):
"""
"""
pass
def start(self):
"""
"""
self.call()
if self.write != self.write_to_failed:
self.call()
self.exit()
@@ -97,8 +121,9 @@ class HoneyPotCommand(object):
"""
try:
self.protocol.cmdstack.pop()
self.protocol.cmdstack[-1].resume()
except AttributeError:
if len(self.protocol.cmdstack):
self.protocol.cmdstack[-1].resume()
except (AttributeError, IndexError):
# Cmdstack could be gone already (wget + disconnect)
pass
@@ -165,6 +190,14 @@ class HoneyPotShell(object):
try:
tok = self.lexer.get_token()
# log.msg( "tok: %s" % (repr(tok)) )
# Ignore parentheses
tok_len = len(tok)
tok = tok.strip('(')
tok = tok.strip(')')
if len(tok) != tok_len and tok == '':
continue
if tok == self.lexer.eof:
if len(tokens):
self.cmdpending.append((tokens))

View File

@@ -195,7 +195,11 @@ class HoneyPotBaseProtocol(insults.TerminalProtocol, TimeoutMixin):
obj.set_input_data(pp.input_data)
self.cmdstack.append(obj)
obj.start()
self.pp.outConnectionLost()
if hasattr(obj, 'safeoutfile'):
if obj.safeoutfile:
self.terminal.redirFiles.add(obj.safeoutfile)
if self.pp:
self.pp.outConnectionLost()
def uptime(self):
@@ -352,19 +356,22 @@ class HoneyPotInteractiveProtocol(HoneyPotBaseProtocol, recvline.HistoricRecvLin
def handle_CTRL_C(self):
"""
"""
self.cmdstack[-1].handle_CTRL_C()
if len(self.cmdstack):
self.cmdstack[-1].handle_CTRL_C()
def handle_CTRL_D(self):
"""
"""
self.cmdstack[-1].handle_CTRL_D()
if len(self.cmdstack):
self.cmdstack[-1].handle_CTRL_D()
def handle_TAB(self):
"""
"""
self.cmdstack[-1].handle_TAB()
if len(self.cmdstack):
self.cmdstack[-1].handle_TAB()
def handle_CTRL_K(self):

View File

@@ -31,6 +31,8 @@ class LoggingServerProtocol(insults.ServerProtocol):
self.ttylogPath = cfg.get('honeypot', 'log_path')
self.downloadPath = cfg.get('honeypot', 'download_path')
self.redirFiles = set()
try:
self.bytesReceivedLimit = int(cfg.get('honeypot',
'download_limit_size'))
@@ -134,14 +136,15 @@ class LoggingServerProtocol(insults.ServerProtocol):
try:
with open(self.stdinlogFile, 'rb') as f:
shasum = hashlib.sha256(f.read()).hexdigest()
shasumfile = self.downloadPath + "/" + shasum
if (os.path.exists(shasumfile)):
shasumfile = os.path.join(self.downloadPath, shasum)
if os.path.exists(shasumfile):
os.remove(self.stdinlogFile)
log.msg("Not storing duplicate content " + shasum)
else:
os.rename(self.stdinlogFile, shasumfile)
os.symlink(shasum, self.stdinlogFile)
log.msg(eventid='cowrie.session.file_download',
format='Saved stdin contents to %(outfile)s',
format='Saved stdin contents with SHA-256 %(shasum)s to %(outfile)s',
url='stdin',
outfile=shasumfile,
shasum=shasum)
@@ -150,6 +153,34 @@ class LoggingServerProtocol(insults.ServerProtocol):
finally:
self.stdinlogOpen = False
if self.redirFiles:
for rf in self.redirFiles:
try:
if not os.path.exists(rf):
continue
if os.path.getsize(rf) == 0:
os.remove(rf)
continue
with open(rf, 'rb') as f:
shasum = hashlib.sha256(f.read()).hexdigest()
shasumfile = os.path.join(self.downloadPath, shasum)
if os.path.exists(shasumfile):
os.remove(rf)
log.msg("Not storing duplicate content " + shasum)
else:
os.rename(rf, shasumfile)
os.symlink(shasum, rf)
log.msg(eventid='cowrie.session.file_download',
format='Saved redir contents with SHA-256 %(shasum)s to %(outfile)s',
url='redir',
outfile=shasumfile,
shasum=shasum)
except IOError:
pass
self.redirFiles.clear()
if self.ttylogOpen:
# TODO: Add session duration to this entry
log.msg(eventid='cowrie.log.closed',

View File

@@ -86,8 +86,7 @@ class Output(cowrie.core.output.Output):
base64.b64decode(self.auth_key), hashlib.sha256).digest())
auth_header = 'credentials={0} nonce={1} userid={2}'.format(digest, _nonceb64, self.userid)
headers = {'X-ISC-Authorization': auth_header,
'Content-Type':'text/plain',
'Content-Length': len(log_output)}
'Content-Type':'text/plain'}
#log.msg(headers)
if self.debug:

136
cowrie/output/mongodb.py Normal file
View File

@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
import pymongo
from twisted.python import log
import cowrie.core.output
class Output(cowrie.core.output.Output):
"""
"""
def __init__(self, cfg):
self.cfg = cfg
cowrie.core.output.Output.__init__(self, cfg)
def insert_one(self, collection, event):
try:
object_id = collection.insert_one(event).inserted_id
return object_id
except Exception as e:
log.msg('mongo error - {0}'.format(e))
def update_one(self, collection, session, doc):
try:
object_id = collection.update({'session': session}, doc)
return object_id
except Exception as e:
log.msg('mongo error - {0}'.format(e))
def start(self):
"""
"""
db_addr = self.cfg.get('output_mongodb', 'connection_string')
db_name = self.cfg.get('output_mongodb', 'database')
try:
self.mongo_client = pymongo.MongoClient(db_addr)
self.mongo_db = self.mongo_client[db_name]
# Define Collections.
self.col_sensors = self.mongo_db['sensors']
self.col_sessions = self.mongo_db['sessions']
self.col_auth = self.mongo_db['auth']
self.col_input = self.mongo_db['input']
self.col_downloads = self.mongo_db['downloads']
self.col_input = self.mongo_db['input']
self.col_clients = self.mongo_db['clients']
self.col_ttylog = self.mongo_db['ttylog']
self.col_keyfingerprints = self.mongo_db['keyfingerprints']
self.col_event = self.mongo_db['event']
except Exception, e:
log.msg('output_mongodb: Error: %s' % str(e))
def stop(self):
"""
"""
self.mongo_client.close()
def write(self, entry):
"""
"""
for i in list(entry.keys()):
# Remove twisted 15 legacy keys
if i.startswith('log_'):
del entry[i]
eventid = entry["eventid"]
if eventid == 'cowrie.session.connect':
# Check if sensor exists, else add it.
doc = self.col_sensors.find_one({'sensor': self.sensor})
if doc:
sensorid = doc['sensor']
else:
sensorid = self.insert_one(self.col_sensors, entry)
# Prep extra elements just to make django happy later on
entry['starttime'] = entry['timestamp']
entry['endtime'] = None
entry['sshversion'] = None
entry['termsize'] = None
log.msg('Session Created')
self.insert_one(self.col_sessions, entry)
elif eventid in ['cowrie.login.success', 'cowrie.login.failed']:
self.insert_one(self.col_auth, entry)
elif eventid in ['cowrie.command.success', 'cowrie.command.failed']:
self.insert_one(self.col_input, entry)
elif eventid == 'cowrie.session.file_download':
# ToDo add a config section and offer to store the file in the db - useful for central logging
# we will add an option to set max size, if its 16mb or less we can store as normal,
# If over 16 either fail or we just use gridfs both are simple enough.
self.insert_one(self.col_downloads, entry)
elif eventid == 'cowrie.client.version':
doc = self.col_sessions.find_one({'session': entry['session']})
if doc:
doc['sshversion'] = entry['version']
self.update_one(self.col_sessions, entry['session'], doc)
else:
pass
elif eventid == 'cowrie.client.size':
doc = self.col_sessions.find_one({'session': entry['session']})
if doc:
doc['termsize'] = '{0}x{1}'.format(entry['width'], entry['height'])
self.update_one(self.col_sessions, entry['session'], doc)
else:
pass
elif eventid == 'cowrie.session.closed':
doc = self.col_sessions.find_one({'session': entry['session']})
if doc:
doc['endtime'] = entry['timestamp']
self.update_one(self.col_sessions, entry['session'], doc)
else:
pass
elif eventid == 'cowrie.log.closed':
# ToDo Compress to opimise the space and if your sending to remote db
with open(entry["ttylog"]) as ttylog:
entry['ttylogpath'] = entry['ttylog']
entry['ttylog'] = ttylog.read().encode('hex')
self.insert_one(self.col_ttylog, entry)
elif eventid == 'cowrie.client.fingerprint':
self.insert_one(self.col_keyfingerprints, entry)
# Catch any other event types
else:
self.insert_one(self.col_event, entry)

View File

@@ -138,6 +138,10 @@ class HoneyPotTelnetAuthProtocol(AuthenticatingTelnetProtocol):
return 'Discard'
def telnet_Command(self, command):
self.transport.protocol.dataReceived(command+'\r')
return "Command"
def _cbLogin(self, ial):
"""
Fired on a successful login
@@ -176,15 +180,17 @@ class HoneyPotTelnetAuthProtocol(AuthenticatingTelnetProtocol):
if opt == ECHO:
return True
elif opt == SGA:
return True
return False
#return True
else:
return False
def enableRemote(self, opt):
if opt == LINEMODE:
self.transport.requestNegotiation(LINEMODE, MODE + chr(TRAPSIG))
return True
return False
#self.transport.requestNegotiation(LINEMODE, MODE + chr(TRAPSIG))
#return True
elif opt == NAWS:
return True
elif opt == SGA:

View File

@@ -343,6 +343,14 @@ logfile = log/cowrie.json
#[output_sqlite]
#db_file = cowrie.db
# MongoDB logging module
#
# MongoDB logging requires an extra Python module: pip install pymongo
#
#[output_mongodb]
#connection_string = mongodb://username:password@host:port/database
#database = dbname
# Splunk SDK output module - EARLY RELEASE NOT RECOMMENDED
# This sends logs directly to Splunk using the Python REST SDK

View File

@@ -7,35 +7,64 @@
* Working Cowrie installation
* Cowrie JSON log file (enable database json in cowrie.cfg)
* Java 8
## Installation
We'll examine simple installation, when we install ELK stack on the same machine that used for cowrie.
* Add Elastic's repository and key
```
wget -qO - https://packages.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
echo "deb https://artifacts.elastic.co/packages/5.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-5.x.list
apt-get update
```
* Install logstash, elasticsearch and kibana
```
apt-get install logstash
apt-get install elasticsearch
````
* Install Kibana
This may be different depending on your operating system. Kibana will need additional components such as a web server
apt-get install elasticsearch logstash kibana
```
* Set them to autostart
```
update-rc.d elasticsearch defaults 95 10
update-rc.d kibana defaults 95 10
```
## ElasticSearch Configuration
TBD
## Kibana Configuration
* Make a folder for logs
```
mkdir /var/log/kibana
chown kibana:kibana /var/log/kibana
```
* Change the following parameters in /etc/kibana/kibana.yml to reflect your server setup:
```
"server.host" - set it to "localhost" if you use nginx for basic authentication or external interface if you use XPack (see below)
"server.name" - name of the server
"elasticsearch.url" - address of the elasticsearch
"elasticsearch.username", "elasticsearch.password" - needed only if you use XPack (see below)
"logging.dest" - set path to logs (/var/log/kibana/kibana.log)
```
## Logstash Configuration
* Download GeoIP data
```
wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
wget http://download.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz
wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz
```
* Place these somewhere in your filesystem.
* Place these somewhere in your filesystem and make sure that "logstash" user can read it
* Configure logstash
@@ -65,3 +94,59 @@ http://<hostname>:9200/_search?q=cowrie&size=5
* If this gives output, your data is correctly loaded into ElasticSearch
* When you successfully configured logstash, remove "file" and "stdout" blocks from output section of logstash configuration.
## Distributed setup of sensors or multiple sensors on the same host
If you have multiple sensors, you will need to setup up FileBeat to feed logstash with logs from all sensors
On the logstash server:
* Change "input" section of the logstash to the following:
```
input {
beats {
port => 5044
}
}
```
On the sensor servers:
* Install filebeat
```
wget -qO - https://packages.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
echo "deb https://artifacts.elastic.co/packages/5.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-5.x.list
apt-get update
apt-get install filebeat
```
* Enable autorun for it
```
update-rc.d filebeat defaults 95 10
```
* Configure filebeat
```
cp filebeat-cowrie.conf /etc/filebeat/filebeat.yml
```
* Check the following parameters
```
paths - path to cowrie's json logs
logstash - check ip of the logstash host
```
* Start filebeat
```
service filebeat start
```
## Tuning ELK stack
* Refer to elastic's documentation about proper configuration of the system for the best elasticsearch's performance
* You may avoid installing nginx for restricting access to the kibana by installing official elastic's plugin called "X-Pack" (https://www.elastic.co/products/x-pack)

View File

@@ -0,0 +1,22 @@
filebeat:
prospectors:
-
paths:
- /home/cowrie/cowrie/log/cowrie.json*
encoding: plain
input_type: log
document_type: cowrie
registry_file: /var/lib/filebeat/registry
output:
logstash:
hosts: ["10.10.0.11:5044"]
shipper:
logging:
to_syslog: false
to_files: true
files:
path: /var/log/filebeat/
name: mybeat
rotateeverybytes: 10485760 # = 10MB
keepfiles: 7
level: info

View File

@@ -1,675 +0,0 @@
{
"title": "Cowrie2ElasticSearch",
"services": {
"query": {
"list": {
"0": {
"query": "*",
"alias": "",
"color": "#7EB26D",
"id": 0,
"pin": false,
"type": "lucene",
"enable": true
}
},
"ids": [
0
]
},
"filter": {
"list": {
"0": {
"type": "terms",
"field": "_type",
"value": "cowrie",
"mandate": "must",
"active": true,
"alias": "",
"id": 0
},
"1": {
"type": "time",
"field": "@timestamp",
"from": "now-30d",
"to": "now",
"mandate": "must",
"active": true,
"alias": "",
"id": 1
}
},
"ids": [
0,
1
]
}
},
"rows": [
{
"title": "Graph",
"height": "250px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 3,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "sensor",
"exclude": [],
"missing": false,
"other": false,
"size": 5,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "table",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "Sensors"
},
{
"error": false,
"span": 3,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "success",
"exclude": [],
"missing": true,
"other": true,
"size": 5,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "table",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "Successes"
}
],
"notice": false
},
{
"title": "Histogram",
"height": "300px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"span": 12,
"editable": true,
"type": "histogram",
"loadingEditor": false,
"mode": "count",
"time_field": "timestamp",
"value_field": null,
"x-axis": true,
"y-axis": true,
"scale": 1,
"y_format": "none",
"grid": {
"max": null,
"min": 0
},
"queries": {
"mode": "all",
"ids": [
0
]
},
"annotate": {
"enable": false,
"query": "*",
"size": 20,
"field": "_type",
"sort": [
"_score",
"desc"
]
},
"auto_int": false,
"resolution": 100,
"interval": "1d",
"intervals": [
"auto",
"1s",
"1m",
"5m",
"10m",
"30m",
"1h",
"3h",
"12h",
"1d",
"1w",
"1y"
],
"lines": true,
"fill": 0,
"linewidth": 3,
"points": false,
"pointradius": 5,
"bars": false,
"stack": true,
"spyable": true,
"zoomlinks": true,
"options": true,
"legend": true,
"show_query": true,
"interactive": true,
"legend_counts": true,
"timezone": "browser",
"percentage": false,
"zerofill": true,
"derivative": false,
"tooltip": {
"value_type": "cumulative",
"query_as_alias": true
},
"title": "Histogram",
"scaleSeconds": false
}
],
"notice": false
},
{
"title": "Usernames",
"height": "300px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 6,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "username.raw",
"exclude": [],
"missing": false,
"other": false,
"size": 20,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "bar",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "Usernames (top 20)"
},
{
"error": false,
"span": 6,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "username.raw",
"exclude": [],
"missing": false,
"other": false,
"size": 20,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "pie",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "Usernames (top 20)"
}
],
"notice": false
},
{
"title": "Passwords",
"height": "300px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 6,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "password.raw",
"exclude": [],
"missing": false,
"other": false,
"size": 20,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "bar",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "Passwords (top 20)"
},
{
"error": false,
"span": 6,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "password.raw",
"exclude": [],
"missing": false,
"other": false,
"size": 20,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "pie",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "Passwords (top 20)"
}
],
"notice": false
},
{
"title": "Clients",
"height": "300px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 6,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "client.raw",
"exclude": [],
"missing": false,
"other": false,
"size": 20,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "bar",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "SSH clients (top 20)"
},
{
"error": false,
"span": 6,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "client.raw",
"exclude": [],
"missing": false,
"other": false,
"size": 20,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "pie",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "SSH clients (top 20)"
}
],
"notice": false
},
{
"title": "Maps",
"height": "450px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 8,
"editable": true,
"type": "map",
"loadingEditor": false,
"map": "world",
"colors": [
"#A0E2E2",
"#265656"
],
"size": 100,
"exclude": [],
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"title": "Attack map (world)",
"field": "country_code2"
},
{
"error": false,
"span": 4,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "geoip.country_name.raw",
"exclude": [],
"missing": false,
"other": true,
"size": 13,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "table",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "count",
"valuefield": "",
"title": "Countries"
}
],
"notice": false
},
{
"title": "ASN",
"height": "150px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"error": false,
"span": 4,
"editable": true,
"type": "terms",
"loadingEditor": false,
"field": "geoip.asn.raw",
"exclude": [],
"missing": false,
"other": true,
"size": 20,
"order": "count",
"style": {
"font-size": "10pt"
},
"donut": false,
"tilt": false,
"labels": true,
"arrangement": "horizontal",
"chart": "table",
"counter_pos": "above",
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"tmode": "terms",
"tstat": "total",
"valuefield": "",
"title": "ASN"
}
],
"notice": false
},
{
"title": "Events",
"height": "650px",
"editable": true,
"collapse": true,
"collapsable": true,
"panels": [
{
"error": false,
"span": 12,
"editable": true,
"group": [
"default"
],
"type": "table",
"size": 100,
"pages": 5,
"offset": 0,
"sort": [
"_score",
"desc"
],
"style": {
"font-size": "9pt"
},
"overflow": "min-height",
"fields": [],
"highlight": [],
"sortable": true,
"header": true,
"paging": true,
"spyable": true,
"queries": {
"mode": "all",
"ids": [
0
]
},
"field_list": true,
"status": "Stable",
"trimFactor": 300,
"normTimes": true,
"title": "Documents",
"all_fields": false,
"localTime": false,
"timeField": "@timestamp"
}
],
"notice": false
}
],
"editable": true,
"index": {
"interval": "day",
"pattern": "[logstash-]YYYY.MM.DD",
"default": "_all",
"warm_fields": false
},
"style": "dark",
"failover": false,
"panel_hints": true,
"loader": {
"save_gist": false,
"save_elasticsearch": true,
"save_local": true,
"save_default": true,
"save_temp": true,
"save_temp_ttl_enable": true,
"save_temp_ttl": "30d",
"load_gist": true,
"load_elasticsearch": true,
"load_elasticsearch_size": 20,
"load_local": true,
"hide": false
},
"pulldowns": [
{
"type": "query",
"collapse": false,
"notice": false,
"query": "*",
"pinned": true,
"history": [],
"remember": 10,
"enable": true
},
{
"type": "filtering",
"collapse": false,
"notice": true,
"enable": true
}
],
"nav": [
{
"type": "timepicker",
"collapse": false,
"notice": false,
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"timefield": "@timestamp",
"enable": true,
"now": true,
"filter_id": 1
}
],
"refresh": false
}

View File

@@ -33,18 +33,7 @@ filter {
geoip {
source => "src_ip"
target => "geoip"
database => "/opt/logstash/vendor/geoip/GeoLiteCity.dat"
add_field => [ "[geoip][coordinates]", "%{[geoip][longitude]}" ]
add_field => [ "[geoip][coordinates]", "%{[geoip][latitude]}" ]
}
geoip {
source => "src_ip"
database => "/opt/logstash/vendor/geoip/GeoIPASNum.dat"
}
mutate {
convert => [ "[geoip][coordinates]", "float" ]
database => "/opt/logstash/vendor/geoip/GeoLite2-City.dat"
}
}
}

View File

@@ -9,7 +9,7 @@ Wants=mysql.service
Type=forking
User=cowrie
Group=cowrie
PIDFile=var/run/cowrie.pid
PIDFile=/home/cowrie/cowrie/var/run/cowrie.pid
ExecStart=/home/cowrie/cowrie/start.sh cowrie-env
ExecStop=/home/cowrie/cowrie/stop.sh
ExecReload=/home/cowrie/cowrie/stop.sh && sleep 10 && /home/cowrie/cowrie/start.sh cowrie-env

View File

@@ -5,25 +5,10 @@ udev /dev devtmpfs rw,relatime,size=10240k,nr_inodes=997843,mode=755 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
tmpfs /run tmpfs rw,nosuid,relatime,size=1613336k,mode=755 0 0
/dev/dm-0 / ext3 rw,relatime,errors=remount-ro,data=ordered 0 0
securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0
tmpfs /run/lock tmpfs rw,nosuid,nodev,noexec,relatime,size=5120k 0 0
tmpfs /sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec,mode=755 0 0
cgroup /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd 0 0
pstore /sys/fs/pstore pstore rw,nosuid,nodev,noexec,relatime 0 0
cgroup /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0
cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0
cgroup /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0
cgroup /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0
cgroup /sys/fs/cgroup/net_cls,net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_cls,net_prio 0 0
cgroup /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0
cgroup /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0
systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=22,pgrp=1,timeout=300,minproto=5,maxproto=5,direct 0 0
mqueue /dev/mqueue mqueue rw,relatime 0 0
hugetlbfs /dev/hugepages hugetlbfs rw,relatime 0 0
debugfs /sys/kernel/debug debugfs rw,relatime 0 0
fusectl /sys/fs/fuse/connections fusectl rw,relatime 0 0
/dev/sda1 /boot ext2 rw,relatime 0 0
/dev/mapper/home /home ext3 rw,relatime,data=ordered 0 0
binfmt_misc /proc/sys/fs/binfmt_misc binfmt_misc rw,relatime 0 0
tmpfs /run/user/1000 tmpfs rw,nosuid,nodev,relatime,size=806668k,mode=700,uid=1000,gid=1000 0 0

View File

@@ -10,6 +10,9 @@ pyes
# mysql
MySQL-python
# mongodb
pymongo
# rethinkdblog
rethinkdb