mirror of
https://github.com/aljazceru/cowrie.git
synced 2026-01-08 16:54:30 +01:00
add dshield output support
This commit is contained in:
@@ -236,7 +236,17 @@ logfile = log/cowrie.json
|
||||
#index = cowrie
|
||||
#type = cowrie
|
||||
|
||||
# Local Syslog output module
|
||||
# Send statics to SANS
|
||||
# https://isc.sans.edu/ssh.html
|
||||
# You must signup for an api key.
|
||||
|
||||
#[output_dshield]
|
||||
#auth_key = auth_key_here
|
||||
#userid = userid_here
|
||||
#batch_size = 100
|
||||
|
||||
|
||||
## Local Syslog output module
|
||||
#
|
||||
# This sends log messages to the local syslog daemon.
|
||||
# Facility can be:
|
||||
|
||||
123
cowrie/output/dshield.py
Normal file
123
cowrie/output/dshield.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# Simple elasticsearch logger
|
||||
|
||||
import os
|
||||
import json
|
||||
import dateutil.parser
|
||||
import time
|
||||
import base64
|
||||
import hmac
|
||||
import hashlib
|
||||
import requests
|
||||
import re
|
||||
|
||||
from twisted.python import log
|
||||
from twisted.internet import threads, reactor
|
||||
|
||||
import cowrie.core.output
|
||||
|
||||
|
||||
class Output(cowrie.core.output.Output):
|
||||
|
||||
def __init__(self, cfg):
|
||||
self.auth_key = cfg.get('output_dshield', 'auth_key')
|
||||
self.userid = cfg.get('output_dshield', 'userid')
|
||||
self.batch_size = int(cfg.get('output_dshield', 'batch_size'))
|
||||
cowrie.core.output.Output.__init__(self, cfg)
|
||||
|
||||
def start(self):
|
||||
self.batch = [] # this is used to store login attempts in batches
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def write(self, entry):
|
||||
for i in entry.keys():
|
||||
# remove twisted 15 legacy keys
|
||||
if i.startswith('log_'):
|
||||
del entry[i]
|
||||
|
||||
# we only want login events
|
||||
if entry["eventid"] == 'KIPP0002' or entry["eventid"] == 'KIPP0003':
|
||||
date = dateutil.parser.parse(entry["timestamp"])
|
||||
self.batch.append({
|
||||
'date' : date.date().__str__(),
|
||||
'time' : date.time().strftime("%H:%M:%S"),
|
||||
'timezone' : time.strftime("%z"),
|
||||
'source_ip' : entry['src_ip'],
|
||||
'user' : entry['username'],
|
||||
'password' : entry['password'],
|
||||
})
|
||||
|
||||
if len(self.batch) >= self.batch_size:
|
||||
batch_to_send = self.batch
|
||||
self.submit_entries(batch_to_send)
|
||||
self.batch = []
|
||||
|
||||
def transmission_error(self, batch):
|
||||
self.batch.extend(batch) # adds all the events we just tried to send back in again
|
||||
if len(self.batch) > self.batch_size * 2:
|
||||
self.batch = self.batch[-self.batch_size:]
|
||||
|
||||
def submit_entries(self, batch):
|
||||
'''
|
||||
Large parts of this method are adapted from kippo-pyshield by jkakavas
|
||||
Many thanks to their efforts.
|
||||
source: https://github.com/jkakavas/kippo-pyshield
|
||||
'''
|
||||
log_output = ''
|
||||
# Format login attempts as tab seperated log entries
|
||||
for attempt in self.batch:
|
||||
log_output += '{0}\t{1}\t{2}\t{3}\t{4}\t{5}\n'.format(attempt['date'],attempt['time'],attempt['timezone'],attempt['source_ip'],attempt['user'],attempt['password'])
|
||||
# The nonce is predefined as explained in the original script :
|
||||
# trying to avoid sending the authentication key in the "clear" but not wanting to
|
||||
# deal with a full digest like exchange. Using a fixed nonce to mix up the limited
|
||||
# userid.
|
||||
nonce = base64.b64decode('ElWO1arph+Jifqme6eXD8Uj+QTAmijAWxX1msbJzXDM=')
|
||||
digest = base64.b64encode(hmac.new('{0}{1}'.format(nonce,self.userid),base64.b64decode(self.auth_key), hashlib.sha256).digest())
|
||||
auth_header = 'credentials={0} nonce=ElWO1arph+Jifqme6eXD8Uj+QTAmijAWxX1msbJzXDM= userid={1}'.format(digest, self.userid)
|
||||
headers = {'X-ISC-Authorization': auth_header,
|
||||
'Content-Type':'text/plain',
|
||||
'Content-Length': len(log_output)}
|
||||
log.msg(headers)
|
||||
req = threads.deferToThread(requests.request,
|
||||
method ='PUT',
|
||||
url = 'https://secure.dshield.org/api/file/sshlog',
|
||||
headers = headers,
|
||||
timeout = 10,
|
||||
data = log_output)
|
||||
|
||||
def check_response(resp):
|
||||
failed = False
|
||||
response = resp.content
|
||||
if resp.status_code == requests.codes.ok:
|
||||
sha1_regex = re.compile(ur'<sha1checksum>([^<]+)<\/sha1checksum>')
|
||||
sha1_match = sha1_regex.search(response)
|
||||
if sha1_match is None:
|
||||
log.err('dshield ERROR: Could not find sha1checksum in response')
|
||||
failed = True
|
||||
sha1_local = hashlib.sha1()
|
||||
sha1_local.update(log_output)
|
||||
if sha1_match.group(1) != sha1_local.hexdigest():
|
||||
log.err('\ndshield ERROR: SHA1 Mismatch {0} {1} .\n'.format(sha1_match.group(1), sha1_local.hexdigest()))
|
||||
failed = True
|
||||
md5_regex = re.compile(ur'<md5checksum>([^<]+)<\/md5checksum>')
|
||||
md5_match = md5_regex.search(response)
|
||||
if md5_match is None:
|
||||
log.err('dshield ERROR: Could not find md5checksum in response')
|
||||
failed = True
|
||||
md5_local = hashlib.md5()
|
||||
md5_local.update(log_output)
|
||||
if md5_match.group(1) != md5_local.hexdigest():
|
||||
log.err('\ndshield ERROR: MD5 Mismatch {0} {1} .\n'.format(md5_match.group(1), md5_local.hexdigest()))
|
||||
failed = True
|
||||
log.msg('\ndshield SUCCESS: Sent {0} bytes worth of data to secure.dshield.org\n'.format(len(log_output)))
|
||||
else:
|
||||
log.err('\ndshield ERROR: error {0} .\n'.format(resp.status_code))
|
||||
log.err('Response was {0}'.format(response))
|
||||
failed = True
|
||||
|
||||
if failed:
|
||||
# something went wrong, we need to add them to batch.
|
||||
reactor.callFromThread(self.transmission_error, batch)
|
||||
|
||||
req.addCallback(check_response)
|
||||
Reference in New Issue
Block a user