diff --git a/cowrie/cowrie.cfg.example b/cowrie/cowrie.cfg.example new file mode 100644 index 0000000000000000000000000000000000000000..88640533ab43a0f1938a2df174f0677f68700ffc --- /dev/null +++ b/cowrie/cowrie.cfg.example @@ -0,0 +1,103 @@ +# ============================================================================ +# General Cowrie Options +# ============================================================================ +[honeypot] + +sensor_name= hugo.example.org +hostname = dbsrv-07.accounting +log_path = var/log +state_path = var/lib +download_path = ${honeypot:state_path}/downloads +share_path = share/cowrie +etc_path = etc +contents_path = honeyfs +txtcmds_path = ${honeypot:share_path}/txtcmds +download_limit_size = 10485760 +ttylog = false +ttylog_path = ${honeypot:state_path}/tty +interactive_timeout = 180 +authentication_timeout = 120 +backend = shell +timezone = UTC +auth_class = UserDB + +# ============================================================================ +# Shell Options +# Options around Cowrie's Shell Emulation +# ============================================================================ +[shell] + +filesystem = ${honeypot:share_path}/fs.pickle +processes = ${honeypot:share_path}/cmdoutput.json +arch = linux-x64-lsb +kernel_version = 3.2.0-4-amd64 +kernel_build_string = #1 SMP Debian 3.2.68-1+deb7u1 +hardware_platform = x86_64 +operating_system = GNU/Linux +ssh_version = OpenSSH_7.9p1, OpenSSL 1.1.1a 20 Nov 2018 + +# ============================================================================ +# SSH Specific Options +# ============================================================================ +[ssh] + +enabled = true +rsa_public_key = ${honeypot:state_path}/ssh_host_rsa_key.pub +rsa_private_key = ${honeypot:state_path}/ssh_host_rsa_key +dsa_public_key = ${honeypot:state_path}/ssh_host_dsa_key.pub +dsa_private_key = ${honeypot:state_path}/ssh_host_dsa_key +version = SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2 +ciphers = aes128-ctr,aes192-ctr,aes256-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc +macs = hmac-sha2-512,hmac-sha2-384,hmac-sha2-56,hmac-sha1,hmac-md5 +compression = zlib@openssh.com,zlib,none +listen_endpoints = systemd:domain=INET6:index=0 systemd:domain=INET6:index=1 +sftp_enabled = true +forwarding = false +forward_redirect = false +forward_tunnel = false +auth_none_enabled = false +auth_keyboard_interactive_enabled = false + +# ============================================================================ +# Telnet Specific Options +# ============================================================================ +[telnet] + +enabled = false + +# ============================================================================ +# Output Plugins +# These provide an extensible mechanism to send audit log entries to third +# parties. The audit entries contain information on clients connecting to +# the honeypot. +# +# Output entries need to start with 'output_' and have the 'enabled' entry. +# ============================================================================ + +# JSON based logging module +# + +[output_jsonlog] + +enabled = false + +# Wardefiler logging module +# + +[output_wardenfiler] + +enabled = true +detector_name = org.example.hugo.cowrie +resolve_nat = no +#reported_public_ipv4 = +#reported_public_ipv6 = +reported_ssh_port = 22 +#nat_host = gateway +#nat_port = 1456 +#anon_mask_4 = 24 +#anon_mask_6 = 64 +aggr_win = 300 +test_mode = true +output_dir = var/spool/warden +port_xlat = 2223:22 2222:2222 +drop_malware = true diff --git a/cowrie/cowrie.service.example b/cowrie/cowrie.service.example new file mode 100644 index 0000000000000000000000000000000000000000..b3ead69176b019c05066bc323c4f08120b40f33c --- /dev/null +++ b/cowrie/cowrie.service.example @@ -0,0 +1,29 @@ +[Unit] +Description=A SSH and Telnet honeypot service +After=network.target +After=rsyslog.service +Requires=cowrie.socket + +[Service] +User=cowrie +Group=cowrie +PIDFile=/opt/cowrie/var/run/cowrie.pid + +Restart=always +RestartSec=5 + +Environment=PYTHONPATH=/opt/cowrie/src +Environment=COWRIE_VIRTUAL_ENV=/opt/cowrie/usr +Environment=TZ="/usr/share/zoneinfo/UTC" +WorkingDirectory=/opt/cowrie + +ExecStart=/opt/cowrie/bin/cowrie start +ExecStop=/opt/cowrie/bin/cowrie stop +ExecRestart=/opt/cowrie/bin/cowrie restart + +StandardOutput=journal +StandardError=journal +SyslogIdentifier=cowrie + +[Install] +WantedBy=multi-user.target diff --git a/cowrie/cowrie.socket.example b/cowrie/cowrie.socket.example new file mode 100644 index 0000000000000000000000000000000000000000..bf8b56b89a78ae8af06f5374de4d18b47ebdf2b7 --- /dev/null +++ b/cowrie/cowrie.socket.example @@ -0,0 +1,7 @@ +[Unit] +Description=Cowrie socket + +[Socket] +ListenStream=2223 +ListenStream=2222 +NoDelay=true diff --git a/cowrie/warden_client_cowrie.cfg b/cowrie/warden_client_cowrie.cfg deleted file mode 100644 index 5668548946e964bee92ae0705d758418ee639ab9..0000000000000000000000000000000000000000 --- a/cowrie/warden_client_cowrie.cfg +++ /dev/null @@ -1,15 +0,0 @@ -{ - "warden": "warden_client.cfg", - "name": "cz.cesnet.server.kippo", - "secret": "", - - "anonymised": "no", - "target_net": "195.113.0.0/16", - - "dbhost": "localhost", - "dbuser": "kippo", - "dbpass": "kippopass", - "dbname": "kippo", - "dbport": 3306, - "awin": 5 -} diff --git a/cowrie/warden_sender_cowrie.py b/cowrie/warden_sender_cowrie.py deleted file mode 100755 index db056aaa18cc2a1e75477e24fffc6c6e950e2872..0000000000000000000000000000000000000000 --- a/cowrie/warden_sender_cowrie.py +++ /dev/null @@ -1,305 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# - -from warden_client import Client, Error, read_cfg, format_timestamp -from time import time, gmtime, strftime -from math import trunc -from uuid import uuid4 -import MySQLdb as my -import MySQLdb.cursors as mycursors -import tempfile, subprocess, base64 -import json -import string -import os -import sys - -#warden client startup -aconfig = read_cfg('warden_client_cowrie.cfg') -wconfig = read_cfg('warden_client.cfg') -aclient_name = aconfig['name'] -wconfig['name'] = aclient_name -aanonymised = aconfig['anonymised'] -aanonymised_net = aconfig['target_net'] -aanonymised = aanonymised if (aanonymised_net != '0.0.0.0/0') or (aanonymised_net == 'omit') else '0.0.0.0/0' -awin = aconfig['awin'] * 60 -atest = aconfig['test_mode'] - -wclient = Client(**wconfig) - -def idea_fill_addresses(event, source_ip, destination_ip, anonymised, anonymised_net): - af = "IP4" if not ':' in source_ip else "IP6" - event['Source'][0][af] = [source_ip] - if anonymised != 'omit': - if anonymised == 'yes': - event['Target'][0]['Anonymised'] = True - event['Target'][0][af] = [anonymised_net] - else: - event['Target'][0][af] = [destination_ip] - return event - -def gen_event_idea_cowrie_info(detect_time, src_ip, dst_ip, win_start_time, win_end_time, aggr_win, conn_count): - event = { - "Format": "IDEA0", - "ID": str(uuid4()), - "DetectTime": detect_time, - "WinStartTime": win_start_time, - "WinEndTime": win_end_time, - "Category": ["Attempt.Login"], - "Note": "SSH login attempt", - "ConnCount": conn_count, - "Source": [{}], - "Target": [{ "Proto": ["tcp", "ssh"], "Port": [22]}], - "Node": [ - { - "Name": aclient_name, - "Type": ["Connection","Honeypot","Recon"], - "SW": ["Cowrie"], - "AggrWin": strftime("%H:%M:%S", gmtime(aggr_win)) - } - ] - } - # Test if we're testing - if atest == "true": - event["Category"].append('Test') - - event = idea_fill_addresses(event, src_ip, dst_ip, aanonymised, aanonymised_net) - return event - - -def gen_event_idea_cowrie_auth(detect_time, src_ip, dst_ip, username, password, sessionid): - - event = { - "Format": "IDEA0", - "ID": str(uuid4()), - "DetectTime": detect_time, - "Category": ["Information.UnauthorizedAccess"], - "Note": "SSH successfull attempt", - "ConnCount": 1, - "Source": [{}], - "Target": [{ "Proto": ["tcp", "ssh"], "Port" : [22] }], - "Node": [ - { - "Name": aclient_name, - "Type": ["Honeypot", "Connection", "Auth"], - "SW": ["Cowrie"], - } - ], - "Attach": [{ "sessionid": sessionid, "username": username, "password": password }] - } - # Test if we're testing - if atest == "true": - event["Category"].append('Test') - - event = idea_fill_addresses(event, src_ip, dst_ip, aanonymised, aanonymised_net) - - return event - - -def gen_event_idea_cowrie_ttylog(detect_time, src_ip, dst_ip, sessionid, ttylog, iinput): - - event = { - "Format": "IDEA0", - "ID": str(uuid4()), - "DetectTime": detect_time, - "Category": ["Information.UnauthorizedAccess"], - "Note": "Cowrie ttylog", - "ConnCount": 1, - "Source": [{}], - "Target": [{ "Proto": ["tcp", "ssh"], "Port" : [22] }], - "Node": [ - { - "Name": aclient_name, - "Type": ["Honeypot", "Data"], - "SW": ["Cowrie"], - } - ], - "Attach": [ { "sessionid": sessionid, "ttylog": ttylog, "iinput": iinput, "smart": iinput } ] - } - # Test if we're testing - if atest == "true": - event["Category"].append('Test') - - event = idea_fill_addresses(event, src_ip, dst_ip, aanonymised, aanonymised_net) - - return event - - -def gen_event_idea_cowrie_download(detect_time, src_ip, dst_ip, sessionid, url, outfile): - - event = { - "Format": "IDEA0", - "ID": str(uuid4()), - "DetectTime": detect_time, - "Category": ["Malware"], - "Note": "Cowrie download", - "ConnCount": 1, - "Source": [{}], - "Target": [{ "Proto": ["tcp", "ssh"], "Port" : [22]}], - "Node": [ - { - "Name": aclient_name, - "Type": ["Honeypot", "Data"], - "SW": ["Cowrie"], - } - ], - "Attach": [{ "sessionid": sessionid, "url": url, "outfile": outfile, "smart": url }] - } - # Test if we're testing - if atest == "true": - event["Category"].append('Test') - - event = idea_fill_addresses(event, src_ip, dst_ip, aanonymised, aanonymised_net) - - return event - - -def get_iinput(sessionid): - ret = [] - query = "SELECT GROUP_CONCAT(input SEPARATOR '--SEP--') as i FROM input WHERE session=%s GROUP BY session;" - crs.execute(query, (sessionid,)) - rows = crs.fetchall() - for row in rows: - ret.append(row["i"]) - return ''.join(ret) - - -def get_ttylog(sessionid): - ret = "" - query = "SELECT id, session, ttylog FROM ttylog WHERE session=%s;" - crs.execute(query, (sessionid,)) - rows = crs.fetchall() - for row in rows: - try: - tf = tempfile.NamedTemporaryFile(delete=False) - with open(tf.name, 'w') as f: - f.write(row['ttylog']) - ret = subprocess.check_output(["/opt/cowrie/bin/playlog", "-m0", tf.name]) - finally: - os.remove(tf.name) - - #try to dumpit to json to see if there are some binary input and perhaps wrap it to base64 - try: - a = json.dumps(ret) - except UnicodeDecodeError as e: - wclient.logger.warning("wraping binary content") - ret = base64.b64encode(ret) - - return ret - - - -con = my.connect( host=aconfig['dbhost'], user=aconfig['dbuser'], passwd=aconfig['dbpass'], - db=aconfig['dbname'], port=aconfig['dbport'], cursorclass=mycursors.DictCursor) -crs = con.cursor() -events = [] - -#kippo vs cowrie -#cowrie/core/dblog.py: def nowUnix(self): -#cowrie/core/dblog.py- """return the current UTC time as an UNIX timestamp""" -#cowrie/core/dblog.py- return int(time.time()) -#kippo/core/dblog.py: def nowUnix(self): -#kippo/core/dblog.py- """return the current UTC time as an UNIX timestamp""" -#kippo/core/dblog.py- return int(time.mktime(time.gmtime()[:-1] + (-1,))) -# k sozalenju -# >>> int(time.mktime(time.gmtime()[:-1] + (-1,)))-int(time.time()) != 0 - - -#old senders data -query = "SELECT UNIX_TIMESTAMP(s.starttime) as starttime, s.ip, COUNT(s.id) as attack_scale, sn.ip as sensor \ - FROM sessions s \ - LEFT JOIN sensors sn ON s.sensor=sn.id \ - WHERE s.starttime > DATE_SUB(UTC_TIMESTAMP(), INTERVAL + %s SECOND) \ - GROUP BY s.ip ORDER BY s.starttime ASC;" - -crs.execute(query, (awin,)) -etime = format_timestamp(time()) -stime = format_timestamp(time() - awin) -rows = crs.fetchall() -for row in rows: - a = gen_event_idea_cowrie_info( - detect_time = format_timestamp(row['starttime']), - src_ip = row['ip'], - dst_ip = row['sensor'], - - win_start_time = stime, - win_end_time = etime, - aggr_win = awin, - conn_count = row['attack_scale'] - ) - events.append(a) - - - -#success login -query = "SELECT UNIX_TIMESTAMP(a.timestamp) as timestamp, s.ip as sourceip, sn.ip as sensor, a.session as sessionid, a.username as username, a.password as password \ - FROM auth a JOIN sessions s ON s.id=a.session JOIN sensors sn ON s.sensor=sn.id \ - WHERE a.success=1 AND a.timestamp > DATE_SUB(UTC_TIMESTAMP(), INTERVAL + %s SECOND) \ - ORDER BY a.timestamp ASC;" - -crs.execute(query, (awin,)) -rows = crs.fetchall() -for row in rows: - a = gen_event_idea_cowrie_auth( - detect_time = format_timestamp(row['timestamp']), - src_ip = row['sourceip'], - dst_ip = row['sensor'], - - username = row['username'], - password = row['password'], - sessionid = row['sessionid'] - ) - events.append(a) - -#ttylog+iinput reporter -query = "SELECT UNIX_TIMESTAMP(s.starttime) as starttime, s.ip as sourceip, sn.ip as sensor, t.session as sessionid \ - FROM ttylog t JOIN sessions s ON s.id=t.session JOIN sensors sn ON s.sensor=sn.id \ - WHERE s.starttime > DATE_SUB(UTC_TIMESTAMP(), INTERVAL + %s SECOND) \ - ORDER BY s.starttime ASC;" - -crs.execute(query, (awin,)) -rows = crs.fetchall() -for row in rows: - a = gen_event_idea_cowrie_ttylog( - detect_time = format_timestamp(row['starttime']), - src_ip = row['sourceip'], - dst_ip = row['sensor'], - - sessionid = row['sessionid'], - ttylog = get_ttylog(row['sessionid']), - iinput = get_iinput(row['sessionid']) - ) - events.append(a) - - -#download -query = "SELECT UNIX_TIMESTAMP(s.starttime) as starttime, s.ip as sourceip, sn.ip as sensor, d.session as sessionid, d.url as url, d.outfile as ofile \ - FROM downloads d JOIN sessions s ON s.id=d.session JOIN sensors sn ON s.sensor=sn.id \ - WHERE s.starttime > DATE_SUB(UTC_TIMESTAMP(), INTERVAL + %s SECOND) \ - ORDER BY s.starttime ASC;" - -crs.execute(query, (awin,)) -rows = crs.fetchall() -for row in rows: - a = gen_event_idea_cowrie_download( - detect_time = format_timestamp(row['starttime']), - src_ip = row['sourceip'], - dst_ip = row['sensor'], - - sessionid = row['sessionid'], - url = row['url'], - outfile = row['ofile'] - ) - events.append(a) - - -print "=== Sending ===" -start = time() -ret = wclient.sendEvents(events) - -if 'saved' in ret: - wclient.logger.info("%d event(s) successfully delivered." % ret['saved']) - -print "Time: %f" % (time() - start) - - diff --git a/cowrie/wardenfiler.py b/cowrie/wardenfiler.py new file mode 100644 index 0000000000000000000000000000000000000000..7f21ec8fadbcef220653c1674793e732d93434fd --- /dev/null +++ b/cowrie/wardenfiler.py @@ -0,0 +1,338 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2019 Cesnet z.s.p.o +# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file. + +""" +Wardenfiler output connector. Writes audit logs to Wardenfiler spool directory in IDEA format +""" + +import os +import errno +import socket +import json +import string +from urllib.parse import urlparse +from time import time, gmtime, strftime +from datetime import datetime +from uuid import uuid4 +from hashlib import sha1 +from base64 import b64encode +from ipaddress import IPv4Network +from ipaddress import IPv6Network +from cowrie.core.config import CowrieConfig +import cowrie.core.output + +class Filer(object): + """ + IDEA files creator + """ + + def __init__(self, directory): + self.basedir = self._ensure_path(directory) + self.tmp = self._ensure_path(os.path.join(self.basedir, "tmp")) + self.incoming = self._ensure_path(os.path.join(self.basedir, "incoming")) + self.hostname = socket.gethostname() + self.pid = os.getpid() + + def _ensure_path(self, p): + try: + os.mkdir(p) + except OSError: + if not os.path.isdir(p): + raise + return p + + def _get_new_name(self, fd=None): + (inode, device) = os.fstat(fd)[1:3] if fd else (0, 0) + return "%s.%d.%f.%d.%d" % ( + self.hostname, self.pid, time(), device, inode) + + def create_unique_file(self): + tmpname = None + while not tmpname: + tmpname = self._get_new_name() + try: + fd = os.open(os.path.join(self.tmp, tmpname), os.O_CREAT | os.O_RDWR | os.O_EXCL) + except OSError as e: + if e.errno != errno.EEXIST: + raise + tmpname = None + newname = self._get_new_name(fd) + os.rename(os.path.join(self.tmp, tmpname), os.path.join(self.tmp, newname)) + nf = os.fdopen(fd, "w") + return nf, newname + + def publish_file(self, short_name): + os.rename(os.path.join(self.tmp, short_name), os.path.join(self.incoming, short_name)) + +class Output(cowrie.core.output.Output): + """ + Wardenfiler Output + """ + detector_name = None + resolve_nat = False + reported_public_ipv4 = None + reported_public_ipv6 = None + reported_ssh_port = None + nat_host = "gateway" + nat_port = 1456 + anon_mask_4 = 32 + anon_mask_6 = 128 + aggr_win = 5 * 60 + test_mode = True + output_dir = "var/spool/warden" + drop_malware = True + win_start = None + attackers = {} + sessions = {} + port_xlat = {} + + + def save_event(self, event): + f, name = self.filer.create_unique_file() + with f: + f.write(json.dumps(event, ensure_ascii=True)) + self.filer.publish_file(name) + + + def start(self): + if CowrieConfig.has_option('output_wardenfiler', 'detector_name'): + self.detector_name = CowrieConfig.get('output_wardenfiler', 'detector_name') + if CowrieConfig.has_option('output_wardenfiler', 'resolve_nat'): + self.resolve_nat = CowrieConfig.getboolean('output_wardenfiler', 'resolve_nat') + if CowrieConfig.has_option('output_wardenfiler', 'reported_public_ipv4'): + self.reported_public_ipv4 = CowrieConfig.get('output_wardenfiler', 'reported_public_ipv4') + if CowrieConfig.has_option('output_wardenfiler', 'reported_public_ipv6'): + self.reported_public_ipv6 = CowrieConfig.get('output_wardenfiler', 'reported_public_ipv6') + if CowrieConfig.has_option('output_wardenfiler', 'reported_ssh_port'): + self.reported_ssh_port = CowrieConfig.getint('output_wardenfiler', 'reported_ssh_port') + if CowrieConfig.has_option('output_wardenfiler', 'nat_host'): + self.nat_host = CowrieConfig.get('output_wardenfiler', 'nat_host') + if CowrieConfig.has_option('output_wardenfiler', 'nat_port'): + self.nat_port = CowrieConfig.getint('output_wardenfiler', 'nat_port') + if CowrieConfig.has_option('output_wardenfiler', 'anon_mask_4'): + self.anon_mask_4 = CowrieConfig.getint('output_wardenfiler', 'anon_mask_4') + if CowrieConfig.has_option('output_wardenfiler', 'anon_mask_6'): + self.anon_mask_6 = CowrieConfig.getint('output_wardenfiler', 'anon_mask_6') + if CowrieConfig.has_option('output_wardenfiler', 'aggr_win'): + self.aggr_win = CowrieConfig.getint('output_wardenfiler', 'aggr_win') + if CowrieConfig.has_option('output_wardenfiler', 'test_mode'): + self.test_mode = CowrieConfig.getboolean('output_wardenfiler', 'test_mode') + if CowrieConfig.has_option('output_wardenfiler', 'output_dir'): + self.output_dir = CowrieConfig.get('output_wardenfiler', 'output_dir') + if CowrieConfig.has_option('output_wardenfiler', 'port_xlat'): + self.port_xlat = dict((int(x), int(y)) for x, y in (e.split(':') for e in CowrieConfig.get('output_wardenfiler', 'port_xlat').split())) + if CowrieConfig.has_option('output_wardenfiler', 'drop_malware'): + self.drop_malware = CowrieConfig.getboolean('output_wardenfiler', 'drop_malware') + + self.filer = Filer(self.output_dir) + + + def stop(self): + """ + No actions needed on honeypot shutdown + """ + + + def write(self, entry): + event = { + "Format": "IDEA0", + "ID": str(uuid4()), + "DetectTime": entry['timestamp'], + "Category": [], + "Source": [{"Proto": ["tcp", "ssh"]}], + "Target": [{ "Proto": ["tcp", "ssh"]}], + "Node": [ + { + "Name": self.detector_name, + "Type": ["Connection", "Auth", "Honeypot"], + "SW": ["Cowrie with Warden Filer output module"], + } + ] + } + + if self.test_mode: + event["Category"].append("Test") + + entry["src_ip"] = entry["src_ip"].lstrip("::ffff:") + if entry.get("dst_ip"): + entry["dst_ip"] = entry["dst_ip"].lstrip("::ffff:") + + # detect IPv4 or IPv6 + af = "IP4" if not ':' in entry["src_ip"] else "IP6" + + # If configured, override destination IP and port + if entry.get("dst_ip"): + if af == "IP4" and self.reported_public_ipv4: + entry["dst_ip"] = self.reported_public_ipv4 + elif af == "IP6" and self.reported_public_ipv6: + entry["dst_ip"] = self.reported_public_ipv6 + + if entry.get("dst_port") and self.reported_ssh_port: + entry["dst_port"] = self.reported_ssh_port + + if entry["eventid"] == 'cowrie.session.connect': + if self.resolve_nat: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((self.nat_host, self.nat_port)) + s.sendall((','.join((entry["src_ip"], str(entry["src_port"]))).encode("utf-8"))) + dst = s.recv(50).decode("utf-8") + s.close() + if dst != "NE": + entry["dst_ip"] = dst + else: + return() + + entry["dst_ip"] = ( + str(IPv4Network("/".join((entry["dst_ip"], str(self.anon_mask_4))), False).network_address) if not ':' in entry["dst_ip"] else + str(IPv6Network("/".join((entry["dst_ip"], str(self.anon_mask_6))), False).network_address) + ) + + entry["loggedin"] = False + self.sessions[entry["session"]] = entry + ws = self.win_start or time() + aid = ','.join((entry["src_ip"], entry["dst_ip"])) + cnt = self.attackers.get(aid, 0) + + if (time() - ws < self.aggr_win): + self.attackers[aid] = cnt + 1 + else: + event["Node"][0]["AggrWin"] = strftime("%H:%M:%S", gmtime(float(self.aggr_win))) + event["WinStartTime"] = datetime.utcfromtimestamp(ws).isoformat() + 'Z' + event["WinEndTime"] = datetime.utcfromtimestamp(ws + self.aggr_win).isoformat() + 'Z' + event["Category"].append("Attempt.Login") + event["Note"] = "SSH login attempt" + for i, c in self.attackers.items(): + src_ip, dst_ip = i.split(',') + af = "IP4" if not ':' in src_ip else "IP6" + event["ID"] = str(uuid4()) + event["DetectTime"] = event["WinEndTime"] + event["ConnCount"] = c + event["Source"] = [{"Proto": ["tcp", "ssh"], af: [src_ip]}] + event["Target"] = [{"Proto": ["tcp", "ssh"], af: [dst_ip]}] + if (self.anon_mask_4 < 32) and (not ':' in entry["dst_ip"]) or (self.anon_mask_6 < 128): + event["Target"][0]["Anonymised"] = True + self.save_event(event) + self.attackers = {} + ws = time() + self.attackers[aid] = 1 + self.win_start = ws + + elif entry["eventid"] == 'cowrie.login.success': + s = entry["session"] + if s in self.sessions: + self.sessions[s]["input"] = [] + self.sessions[s]["loggedin"] = True + + elif entry["eventid"] == 'cowrie.command.input': + s = entry["session"] + if s in self.sessions: + self.sessions[s]["input"].append(entry["input"]) + + elif entry["eventid"] == 'cowrie.session.file_download': + s = entry["session"] + if s in self.sessions: + sch = { "http": 80, "https": 443, "ftp": 21 } + + # deal with the file first (drop even if not reported) + mware = None + fname = None + if "outfile" in entry and os.path.exists(entry["outfile"]): + fp = open(entry["outfile"], "r") + mware = fp.read() + fp.close() + if self.drop_malware: + os.remove(entry["outfile"]) + + if mware: + # TODO: Classify everything as Malware? + event["Category"].append("Malware") + event["Note"] = "Malware download during honeypot session" + + if "url" in entry and entry["url"].startswith(tuple(sch.keys())): + url = urlparse(entry["url"]) + host = url.hostname + ai = socket.getaddrinfo(host, None)[0] + af = "IP6" if ai[0] == socket.AddressFamily.AF_INET6 else "IP4" + ip = ai[4][0] + proto = [ "tcp", url.scheme ] + port = url.port or sch[url.scheme] + + fname = os.path.basename(entry["url"]) + if not fname and 'destfile' in entry: + fname = os.path.basename(entry['destfile']) + + elif not "url" in entry: + # TODO implement the path for other files after discussion + return() + # The remainder of this branch will not execute now + if entry["format"].startswith("Saved redir"): + event["Note"] = "Saved file during honeypot session" + fname = os.path.basename(entry['destfile']) + else: + event["Note"] = "Stdin contents during honeypot session" + # End of the not executed part + + else: + # TODO: Some exotic protocol? Let's not worry with that now + return() + + event["DetectTime"] = entry["timestamp"] + if "url" in entry: + event["Source"][0] = { "Type": ["Malware"] } + event["Source"][0]["URL"] = [entry["url"]] + event["Source"][0][af] = [ip] + event["Source"][0]["Proto"] = proto + event["Source"][0]["Port"] = [port] + if ip != host: + event["Source"][0]["Hostname"] = [host] + else: + # TODO implement later + pass + + del event["Target"] + event["Attach"] = [{ + "Type": ["ShellCode"], + "Hash": ["sha1:" + entry["shasum"]], + "Size": len(mware), + "Note": "Some probably malicious code downloaded during honeypot SSH session", + "ContentEncoding": "base64", + "Content": b64encode(mware.encode()).decode(), + }] + if fname: + event["Attach"][0]["FileName"] = [fname] + if "url" in entry: + event["Attach"][0]["ExternalURI"] = [entry["url"]] + self.save_event(event) + + elif entry["eventid"] == 'cowrie.session.closed': + s = entry["session"] + if s in self.sessions and self.sessions[s]["loggedin"]: + idata = '\n'.join(self.sessions[s]["input"]) + plain = all(c in string.printable for c in idata) + event["Category"].append("Intrusion.UserCompromise") + event["Note"] = "SSH successful login" + (" with unauthorized command input" if len(idata) else "") + af = "IP4" if not ':' in entry["src_ip"] else "IP6" + event["Source"][0][af] = [entry["src_ip"]] + event["Target"][0][af] = [self.sessions[s]["dst_ip"]] + event["Source"][0]["Port"] = [self.sessions[s]["src_port"]] + dst_port = self.sessions[s]["dst_port"] + if dst_port in self.port_xlat: + dst_port = self.port_xlat[dst_port] + event["Target"][0]["Port"] = [dst_port] + if len(idata): + eidata = idata if plain else b64encode(idata.encode()).decode() + attach = { + "Type": ["Exploit"], + "Hash": ["sha1:" + sha1(idata.encode("utf-8")).hexdigest()], + "Size": len(idata), + "Note": "Commands entered by attacker during honeypot SSH session", + "Content": eidata + } + if not plain: + attach["ContentEncoding"] = "base64" + event["Attach"] = [attach] + self.save_event(event) + self.sessions.pop(s, None) diff --git a/dionaea/dionaea.service.example b/dionaea/dionaea.service.example new file mode 100644 index 0000000000000000000000000000000000000000..d592b1fe9f781302f2ad96ef50ae06de196b9df9 --- /dev/null +++ b/dionaea/dionaea.service.example @@ -0,0 +1,22 @@ +[Unit] +Description=A malware capturing honeypot service +After=network.target +After=rsyslog.service +#Requires=dionaea.socket + +[Service] +User=root +Group=root +PIDFile=/opt/dionaea/var/run/dionaea.pid + +Restart=always +RestartSec=5 + +WorkingDirectory=/opt/dionaea + +ExecStart=/opt/dionaea/bin/dionaea -c /opt/dionaea/etc/dionaea/dionaea.cfg -w /opt/dionaea -D -p /opt/dionaea/var/run/dionaea.pid -u dionaea -g dionaea +KillMode=process +Type=forking + +[Install] +WantedBy=multi-user.target diff --git a/dionaea/log_wardenfiler.py b/dionaea/log_wardenfiler.py new file mode 100644 index 0000000000000000000000000000000000000000..b7b34749b152fa3ee888d78a935ff4c8adda0e34 --- /dev/null +++ b/dionaea/log_wardenfiler.py @@ -0,0 +1,410 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2020 Cesnet z.s.p.o +# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file. + +""" +Wardenfiler output connector. Writes audit logs to Wardenfiler spool directory in IDEA format +""" + +import os +import errno +import socket +import json +import hashlib +import logging +import string +from urllib.parse import urlparse +from time import time, gmtime, strftime +from datetime import datetime +from uuid import uuid4 +from hashlib import sha1 +from base64 import b64encode +from ipaddress import IPv4Network +from ipaddress import IPv6Network + +from dionaea import IHandlerLoader +from dionaea.core import ihandler, connection +from dionaea.exception import LoaderError + +logger = logging.getLogger("log_wardenfiler") +logger.setLevel(logging.DEBUG) + +class Filer(object): + """ + IDEA files creator + """ + + def __init__(self, directory): + self.basedir = self._ensure_path(directory) + self.tmp = self._ensure_path(os.path.join(self.basedir, "tmp")) + self.incoming = self._ensure_path(os.path.join(self.basedir, "incoming")) + self.hostname = socket.gethostname() + self.pid = os.getpid() + + def _ensure_path(self, p): + try: + os.mkdir(p) + except OSError: + if not os.path.isdir(p): + raise + return p + + def _get_new_name(self, fd=None): + (inode, device) = os.fstat(fd)[1:3] if fd else (0, 0) + return "%s.%d.%f.%d.%d" % ( + self.hostname, self.pid, time(), device, inode) + + def create_unique_file(self): + tmpname = None + while not tmpname: + tmpname = self._get_new_name() + try: + fd = os.open(os.path.join(self.tmp, tmpname), os.O_CREAT | os.O_RDWR | os.O_EXCL) + except OSError as e: + if e.errno != errno.EEXIST: + raise + tmpname = None + newname = self._get_new_name(fd) + os.rename(os.path.join(self.tmp, tmpname), os.path.join(self.tmp, newname)) + nf = os.fdopen(fd, "w") + return nf, newname + + def publish_file(self, short_name): + os.rename(os.path.join(self.tmp, short_name), os.path.join(self.incoming, short_name)) + + +class LogWardenfilerHandlerLoader(IHandlerLoader): + name = "log_wardenfiler" + + @classmethod + def start(cls, config=None): + try: + return LogWardenfilerHandler("*", config=config) + except LoaderError as e: + logger.error(e.msg, *e.args) + return None + +class LogWardenfilerHandler(ihandler): + detector_name = None + resolve_nat = False + nat_host = "gateway" + nat_port = 1456 + anon_mask_4 = 32 + anon_mask_6 = 128 + aggr_win = 5 * 60 + test_mode = True + output_dir = "var/spool/warden" + drop_malware = True + win_start = None + attackers = {} + sessions = {} + + def __init__(self, path, config = None): + logger.debug("%s ready!", self.__class__.__name__) + ihandler.__init__(self, path) + self.path = path + self._config = config + + def _save_event(self, event): + f, name = self.filer.create_unique_file() + with f: + f.write(json.dumps(event, ensure_ascii = True)) + self.filer.publish_file(name) + + def start(self): + if 'detector_name' in self._config: + self.detector_name = self._config.get('detector_name') + if 'resolve_nat' in self._config: + self.resolve_nat = self._config.get('resolve_nat') + if 'nat_host' in self._config: + self.nat_host = self._config.get('nat_host') + if 'nat_port' in self._config: + self.nat_port = self._config.get('nat_port') + if 'anon_mask_4' in self._config: + self.nat_port = self._config.get('anon_mask_4') + if 'anon_mask_6' in self._config: + self.nat_port = self._config.get('anon_mask_6') + if 'aggr_win' in self._config: + self.aggr_win = self._config.get('aggr_win') + if 'test_mode' in self._config: + self.test_mode = self._config.get('test_mode') + if 'output_dir' in self._config: + self.output_dir = self._config.get('output_dir') + if 'drop_malware' in self._config: + self.drop_malware = self._config.get('drop_malware') + + self.filer = Filer(self.output_dir) + + def _aggregate(self): + ws = self.win_start or time() + if (time() - ws >= self.aggr_win): + logger.info("Counting attacks: %s" % json.dumps(self.attackers, ensure_ascii = True)) + we = datetime.utcfromtimestamp(ws + self.aggr_win).isoformat() + 'Z' + sevent = { + "Format": "IDEA0", + "WinStartTime": datetime.utcfromtimestamp(ws).isoformat() + 'Z', + "WinEndTime": we, + "DetectTime": we, + "Category": [], + "Node": [ + { + "Name": self.detector_name, + "Type": ["Connection", "Auth", "Honeypot"], + "SW": ["Dionaea with Warden Filer output module"], + "AggrWin": strftime("%H:%M:%S", gmtime(float(self.aggr_win))) + } + ] + } + if self.test_mode: + sevent["Category"].append("Test") + + for i, a in self.attackers.items(): + c = a["count"] + if c > 1: + src_ip, dst_ip, dst_port, proto = i.split(',') + if (self.anon_mask_4 < 32) and (not ':' in dst_ip) or (self.anon_mask_6 < 128): + Target[0]["Anonymised"] = "true" + sevent["ID"] = str(uuid4()) + if len(a["creds"]): + sevent["Category"] = ["Recon.Scanning"] + sevent["Note"] = "Successful logins to honeypoted service." + else: + sevent["Category"] = ["Attempt.Login"] + sevent["Note"] = "Connection attempts to IPs assigned to honeypot." + sevent["ConnCount"] = c + af = "IP4" if not ':' in src_ip else "IP6" + proto = [proto] + if a["proto"] + proto.append(a["proto"]) + sevent["Source"] = [{"Proto": proto, af: [src_ip], "Port": a["sports"]}] + sevent["Target"] = [{"Proto": proto, af: [dst_ip], "Port": [int(dst_port)]}] + if len(a["creds"]): + attach = { + "Type": ["Credentials"], + "Note": "Credentials used by attacker used for simulated honeypot login", + "Credentials": a["creds"] + } + sevent["Attach"] = attach + self._save_event(sevent) + logger.info("sending scanning event for %s probing %s (%i times)" % (src_ip, dst_ip, c)) + self.attackers = {} + self.win_start = time() + + def _make_idea(self, con): + s = self.sessions[con] + proto = [s["trans"]] + if s["proto"]: + proto.append(s["proto"]) + event = { + "Format": "IDEA0", + "ID": s["id"], + "DetectTime": s["dt"], + "Category": s["cat"], + "Source": [{"Proto": proto, s["af"]: [s["src_ip"]], "Port": [s["src_port"]]}], + "Target": [{ "Proto": proto, s["af"]: [s["dst_ip"]], "Port": [s["dst_port"]]}], + "Node": [ + { + "Name": self.detector_name, + "Type": ["Connection", "Auth", "Honeypot"], + "SW": ["Dionaea with Warden Filer output module"], + } + ] + } + + if s["anon"]: + event["Target"][0]["Anonymised"] = "true" + + if len(s["creds"]): + p = { + "ftp": "FTP", + "mysql": "MySQL", + "ms-sql-s": "MSSQL", + } + event["Category"].append("Intrusion.UserCompromise") + if s["proto"]: + event["Note"] = p[s["proto"]] + "successful login" + else + event["Note"] = "Successful login attempt" + attach = { + "Type": ["Credentials"], + "Note": "Credentials used by attacker used for simulated honeypot login", + "Credentials": s["creds"] + } + event["Attach"] = [attach] + + if len(s["cmds"]): + event["Category"].append("Attempt.Exploit") + event["Note"] += " with unauthorized command input" + idata = "\n".join(str(c) for c in s[cmds]) + plain = all(c in string.printable for c in idata) + eidata = idata if plain else b64encode(idata.encode()).decode() + attach = { + "Type": ["Exploit"], + "Hash": ["sha1:" + sha1(idata.encode("utf-8")).hexdigest()], + "Size": len(idata), + "Note": "Commands entered by attacker during honeypot session", + "Content": eidata + } + if not plain: + attach["ContentEncoding"] = "base64" + event["Attach"].append(attach) + + return(event) + + def _register_connection(self, con, proto = None, cred = None, cmd = None) + if not con in self.sessions: + src_ip = con.remote.host.lstrip("::ffff:") + dst_ip = con.local.host.lstrip("::ffff:") + + if self.resolve_nat: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((self.nat_host, self.nat_port)) + s.sendall((','.join((src_ip, str(con.local.port))).encode("utf-8"))) + dst = s.recv(50).decode("utf-8") + s.close() + if dst != "NE": + dst_ip = dst + else: + logger.warn("no translation for %s:%s" % (src_ip, con.local.port)) + return() + + af = "IP4" if not ':' in src_ip else "IP6" + anon = (self.anon_mask_4 < 32) and (not ':' in dst_ip) or (self.anon_mask_6 < 128) + if anon: + dst_ip = [( + str(IPv4Network("/".join((dst_ip, str(self.anon_mask_4))), False).network_address) if not ':' in dst_ip else + str(IPv6Network("/".join((dst_ip, str(self.anon_mask_6))), False).network_address) + )] + + self.sessions[con]["id"] = str(uuid4()) + self.sessions[con]["dt"] = datetime.utcnow().isoformat() + "Z" + self.sessions[con]["cat"] = ["Test"] if self.test_mode else [] + self.sessions[con]["af"] = af + self.sessions[con]["anon"] = anon + self.sessions[con]["src_ip"] = src_ip + self.sessions[con]["dst_ip"] = dst_ip + self.sessions[con]["src_port"] = con.remote.port + self.sessions[con]["dst_port"] = con.local.port + self.sessions[con]["trans"] = con.transport + self.sessions[con]["proto"] = None + self.sessions[con]["creds"] = [] + self.sessions[con]["cmds"] = [] + + aid = ','.join((src_ip, dst_ip, str(con.local.port), con.transport)) + + if not aid in in self.attackers: + self.attackers[aid] = { + "count": 0, + "sports": [], + "creds": [], + "proto": None + } + + self.attackers[aid]["count"] += 1 + if not con.remote.port in self.attackers[aid]["sports"]: + self.attackers[aid]["sports"].append(con.remote.port) + if proto: + self.sessions[con]["proto"] = proto + self.attackers[aid]["proto"] = proto + if cred: + self.sessions[con]["creds"].append(cred) + self.attackers[aid]["creds"].append(cred) + if cmd: + self.sessions[con]["cmds"].append(cmd) + + def handle_incident(self, icd): + pass + + def handle_incident_dionaea_connection_tcp_listen(self, icd): + pass; + + def handle_incident_dionaea_connection_tls_listen(self, icd): + pass + + def handle_incident_dionaea_connection_tcp_connect(self, icd): + con = icd.con + self._register_connection(con) + logger.info("connect connection to %s/%s:%i from %s:%i" % (con.remote.host, con.remote.hostname, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_connection_tls_connect(self, icd): + con = icd.con + self._register_connection(con, "ssl-tls") + logger.info("connect connection to %s/%s:%i from %s:%i" % (con.remote.host, con.remote.hostname, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_connection_udp_connect(self, icd): + con = icd.con + self._register_connection(con) + logger.info("connect connection to %s/%s:%i from %s:%i" % (con.remote.host, con.remote.hostname, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_connection_tcp_accept(self, icd): + con = icd.con + self._register_connection(con) + logger.info("accepted connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_connection_tls_accept(self, icd): + con = icd.con + self._register_connection(con, "ssl-tls") + logger.info("accepted connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_connection_tcp_reject(self, icd): + con = icd.con + self._register_connection(con) + logger.info("reject connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_modules_python_ftp_command(self, icd): + con = icd.con + cmd = icd.command.decode() + if hasattr(icd, 'arguments'): + cmd = " ".join([cmd], icd.arguments) + self._register_connection(con, "ftp", cmd = cmd) + logger.info("new FTP command within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_modules_python_mssql_cmd(self, icd): + con = icd.con + self._register_connection(con, "ms-sql-s", cmd = icd.cmd) + logger.info("new MSSQL command within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_modules_python_mysql_command(self, icd): + con = icd.con + cmd = icd.command + if hasattr(icd, 'args'): + cmd = " ".join([cmd], icd.args) + self._register_connection(con, "mysql", cmd = cmd) + logger.info("new MYSQL command within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_modules_python_ftp_login(self, icd): + con = icd.con + self._register_connection(con, "ftp", cred = {"User": icd.username, "Password": icd.password}) + logger.info("new FTP login within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_modules_python_mssql_login(self, icd): + con = icd.con + self._register_connection(con, "ms-sql-s", cred = {"User": icd.username, "Password": icd.password}) + logger.info("new MSSQL login within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_modules_python_mysql_login(self, icd): + con = icd.con + self._register_connection(con, "mysql", cred = {"User": icd.username, "Password": icd.password}) + logger.info("new MySQL login within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + + def handle_incident_dionaea_modules_python_p0f(self, icd): + pass; + + def handle_incident_dionaea_connection_free(self, icd): + con = icd.con + + self._aggregate() + + if con in self.sessions: + s = self.sessions[con] + if len(s["cmds"]): + event = self._make_idea(con) + self._save_event(event) + logger.info("sending connection event from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + self.sessions.pop(con, None) + logger.info("closing connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port)) + else: + logger.warn("no attack data for %s:%s" % (con.remote.host, con.remote.port)) + diff --git a/dionaea/log_wardenfiler.yaml.example b/dionaea/log_wardenfiler.yaml.example new file mode 100644 index 0000000000000000000000000000000000000000..3d96b513589b1e18965dfb27e2fccef15a30779f --- /dev/null +++ b/dionaea/log_wardenfiler.yaml.example @@ -0,0 +1,13 @@ +- name: log_wardenfiler + config: + detector_name: "org.example.hugo.dionaea" + resolve_nat: no + # nat_host: "gateway" + # nat_port: 1456 + # anon_mask_4: 24 + # anon_mask_6: 64 + aggr_win: 300 + test_mode: True + output_dir: "var/spool/warden" + drop_malware: True + drop_stream: True diff --git a/dionaea/warden3-dio-sender.py b/dionaea/warden3-dio-sender.py deleted file mode 100755 index b992ad495c2bad5a742968758b206231799c017f..0000000000000000000000000000000000000000 --- a/dionaea/warden3-dio-sender.py +++ /dev/null @@ -1,246 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2011-2018 Cesnet z.s.p.o -# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file. - -from warden_client import Client, Error, read_cfg, format_timestamp -import json -import string -import urllib -from time import time, gmtime, strftime, sleep -from math import trunc -from uuid import uuid4 -from os import path, remove -import base64 -import sqlite3 -import sys -import re - -aconfig = read_cfg('warden_client_dio.cfg') -wconfig = read_cfg('warden_client.cfg') -aclient_name = aconfig['name'] -wconfig['name'] = aclient_name -aanonymised = aconfig['anonymised'] -aanonymised_net = aconfig['target_net'] -aanonymised = aanonymised if (aanonymised_net != '0.0.0.0/0') or (aanonymised_net == 'omit') else '0.0.0.0/0' -atest = aconfig['test_mode'] - -awin = aconfig['awin'] * 60 -abinpath = aconfig['binaries_path'] -adbfile = aconfig['dbfile'] -aconattempts = aconfig['con_attempts'] -aretryinterval = aconfig['con_retry_interval'] -areportbinaries = aconfig['report_binaries'] -apurgebinaries = aconfig['purge_binaries'] - -wconfig['secret'] = aconfig.get('secret', '') -wclient = Client(**wconfig) - -def idea_fill_addresses(event, source_ip, destination_ip, anonymised, anonymised_net): - af = "IP4" if not ':' in source_ip else "IP6" - event['Source'][0][af] = [source_ip] - if anonymised != 'omit': - if anonymised == 'yes': - event['Target'][0]['Anonymised'] = True - event['Target'][0][af] = [anonymised_net] - else: - event['Target'][0][af] = [destination_ip] - - return event - -def gen_attach_idea_smb(logger, report_binaries, purge_binaries, binaries_path, filename, hashtype, hashdigest, vtpermalink, avref): - - refs = [] - attach = { - "Handle": 'att1', - "FileName": [filename], - "Type": ["Malware"], - "Hash": ["%s:%s" % (hashtype, hashdigest)], - } - - if vtpermalink is not None: - refs.append('url:' + vtpermalink) - - if avref is not None: - refs.extend(avref.split(';')) - - if refs: - refs = [urllib.quote(ref, safe=':') for ref in refs] - refs = list(set(refs)) - attach['Ref'] = refs - - if report_binaries == 'true': - try: - fpath = path.join(binaries_path, hashdigest) - with open(fpath, "r") as f: - fdata = f.read() - attach['ContentType'] = 'application/octet-stream' - attach['ContentEncoding'] = 'base64' - attach['Size'] = len(fdata) - attach['Content'] = base64.b64encode(fdata) - except (IOError) as e: - logger.info("Reading id file \"%s\" with malware failed, information will not be attached." % (fpath)) - if purge_binaries == 'true': - try: - remove(filename) - except OSError: - pass - - return attach - -def gen_attach_idea_db(logger, data): - - attach = {} - attach["Handle"] = 'att1' - attach["Type"] = ["Malware"] - attach['ContentType'] = 'application/octet-stream' - attach['ContentEncoding'] = 'base64' - attach['Size'] = len(data) - attach['Content'] = base64.b64encode(data) - - return attach - -def gen_event_idea_dio(logger, binaries_path, report_binaries, purge_binaries, client_name, anonymised, target_net, detect_time, win_start_time, win_end_time, aggr_win, data): - - category = [] - event = { - "Format": "IDEA0", - "ID": str(uuid4()), - "DetectTime": detect_time, - "WinStartTime": win_start_time, - "WinEndTime": win_end_time, - "ConnCount": data['attack_scale'], - "Source": [{}], - "Target": [{}], - "Node": [ - { - "Name": client_name, - "Type": ["Connection", "Protocol", "Honeypot"], - "SW": ["Dionaea"], - "AggrWin": strftime("%H:%M:%S", gmtime(aggr_win)) - } - ] - } - - # Save TCP/UDP proto - proto = [data['proto']] - - # smbd allows save malware - if data['service'] == 'smbd' and data['download_md5_hash'] is not None: - category.append('Attempt.Exploit') - category.append('Malware') - proto.append('smb') - - if data['download_url'] != '': - event['Source'][0]['URL'] = [data['download_url']] - filename = data['download_url'].split('/')[-1] - - if filename != '' and data['download_md5_hash'] != '': - # Generate "SMB Attach" part of IDEA - a = gen_attach_idea_smb(logger, report_binaries, binaries_path, filename, "md5", data['download_md5_hash'], data['virustotal_permalink'], data['scan_result']) - - event['Source'][0]['AttachHand'] = ['att1'] - event['Attach'] = [a] - - if data['service'] == 'mysqld': - #Clean exported data - if data['mysql_query'] is not None: - mysql_data = re.sub("select @@version_comment limit 1,?", "", data['mysql_query']) - if mysql_data != "": - # Generate "MySQL Attach" part of IDEA - a = gen_attach_idea_db(logger, mysql_data) - - category.append('Attempt.Exploit') - proto.append('mysql') - event['Source'][0]['AttachHand'] = ['att1'] - event['Attach'] = [a] - - if data['service'] == 'mssqld': - #Clean exported data - if data['mssql_query'] is not None: - mssql_data = data['mssql_query'] - if mssql_data != "": - # Generate "MSSQL Attach" part of IDEA - a = gen_attach_idea_db(logger, mssql_data) - - category.append('Attempt.Exploit') - proto.append('mssql') - event['Source'][0]['AttachHand'] = ['att1'] - event['Attach'] = [a] - - event['Source'][0]['Port'] = [data['src_port']] - event['Target'][0]['Port'] = [data['dst_port']] - event['Source'][0]['Proto'] = proto - event['Target'][0]['Proto'] = proto - - idea_fill_addresses(event, data['src_ip'], data['dst_ip'], aanonymised, aanonymised_net) - - # Add default category - if not category: - category.append('Recon.Scanning') - - # Test if we're testing - if atest == "true": - category.append('Test') - - event['Category'] = category - - return event - -def main(): - - con = sqlite3.connect(adbfile) - con.row_factory = sqlite3.Row - con.text_factory = str - crs = con.cursor() - - events = [] - - query = "SELECT c.connection_timestamp AS timestamp, c.remote_host AS src_ip, c.remote_port AS src_port, c.connection_transport AS proto, \ - c.local_host AS dst_ip, c.local_port AS dst_port, COUNT(c.connection) as attack_scale, c.connection_protocol AS service, d.download_url, d.download_md5_hash, \ - v.virustotal_permalink, GROUP_CONCAT('urn:' || vt.virustotalscan_scanner || ':' || vt.virustotalscan_result,';') AS scan_result, \ - group_concat(mca.mysql_command_arg_data) as mysql_query, \ - group_concat(msc.mssql_command_cmd) as mssql_query \ - FROM connections AS c LEFT JOIN downloads AS d ON c.connection = d.connection \ - LEFT JOIN virustotals AS v ON d.download_md5_hash = v.virustotal_md5_hash \ - LEFT JOIN virustotalscans vt ON v.virustotal = vt.virustotal \ - LEFT JOIN mysql_commands mc ON c.connection = mc.connection \ - LEFT JOIN mysql_command_args mca ON mc.mysql_command = mca.mysql_command \ - LEFT JOIN mssql_commands msc ON c.connection = msc.connection \ - WHERE datetime(connection_timestamp,'unixepoch') > datetime('now','-%d seconds') AND c.remote_host != '' \ - GROUP BY c.remote_host, c.local_port ORDER BY c.connection_timestamp ASC;" % (awin) - - attempts = 0 - while attempts < aconattempts: - try: - crs.execute(query) - break - except sqlite3.Error, e: - attempts += 1 - wclient.logger.info("Info: %s - attempt %d/%d." % (e.args[0], attempts, aconattempts)) - if attempts == aconattempts: - wclient.logger.error("Error: %s (dbfile: %s)" % (e.args[0], adbfile)) - - sleep(aretryinterval) - - rows = crs.fetchall() - - if con: - con.close - - etime = format_timestamp(time()) - stime = format_timestamp(time() - awin) - - for row in rows: - dtime = format_timestamp(row['timestamp']) - events.append(gen_event_idea_dio(logger = wclient.logger, binaries_path = abinpath, report_binaries = areportbinaries, purge_binaries = apurgebinaries, client_name = aclient_name, anonymised = aanonymised, target_net = aanonymised_net, detect_time = dtime, win_start_time = stime, win_end_time = etime, aggr_win = awin, data = row)) - - start = time() - ret = wclient.sendEvents(events) - if 'saved' in ret: - wclient.logger.info("%d event(s) successfully delivered in %d seconds" % (ret['saved'], (time() - start))) - - -if __name__ == "__main__": - main() diff --git a/dionaea/warden_client-dio.cfg b/dionaea/warden_client-dio.cfg deleted file mode 100644 index 537d96ab12e92c58163b9c2c1a34e8962bc6f714..0000000000000000000000000000000000000000 --- a/dionaea/warden_client-dio.cfg +++ /dev/null @@ -1,15 +0,0 @@ -{ - "warden": "warden_client.cfg", - "name": "cz.cesnet.server.dionaea", - "secret": "", - - "anonymised": "no", - "target_net": "195.113.0.0/16", - - "dbfile": "/opt/dionaea/var/dionaea/logsql.sqlite", - "binaries_path" : "/opt/dionaea/var/dionaea/binaries", - "report_binaries" : "true", - "con_attempts" : 3, - "con_retry_interval" : 5, - "awin": 5 -}