Skip to content
Snippets Groups Projects
Commit baa347b7 authored by Pavel Eis's avatar Pavel Eis Committed by Pavel Kácha
Browse files

Added connector for Suricata IDS.

parent 7c2e3e58
No related branches found
No related tags found
No related merge requests found
import socket
import json
from uuid import uuid4
import re
import optparse
import sys
import os
import signal
import resource
import os.path as pth
import atexit
import time
from collections import OrderedDict
import logging
class FileWatcher(object):
def __init__(self, filename, tail=True):
self.filename = filename
self.open()
self.line_buffer = ""
if tail and self.f:
self.f.seek(0, os.SEEK_END)
def open(self):
'''
opens file, stored in self.filename and stores i-node and size of file to object
:return: nothing
'''
try:
self.f = open(self.filename, "r")
st = os.fstat(self.f.fileno()) # stores file stats get from filedescriptor
self.inode, self.size = st.st_ino, st.st_size
except IOError:
self.f = None
self.inode = -1
self.size = -1
def _check_reopen(self):
'''
if i-node or size of opened filed changed(someone renamed it, deleted it), then reopen file
:return: nothing
'''
try:
st = os.stat(self.filename)
cur_inode, cur_size = st.st_ino, st.st_size
except OSError as e:
cur_inode = -1
cur_size = -1
if cur_inode != self.inode or cur_size < self.size:
self.close()
self.open()
def readline(self):
'''
opens or reopens file and reads one line from it and appends it to self.line_buffer
:return: self.line_buffer
'''
if not self.f:
self.open()
if not self.f:
return self.line_buffer
res = self.f.readline()
if not res:
self._check_reopen()
if not self.f:
return self.line_buffer
res = self.f.readline()
if not res.endswith("\n"):
self.line_buffer += res
else:
res = self.line_buffer + res
self.line_buffer = ""
return res
def close(self):
try:
if self.f:
self.f.close()
except IOError:
pass
self.inode = -1
self.size = -1
def __repr__(self):
return '%s("%s")' % (type(self).__name__, self.filename)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
return False
def __iter__(self):
return self
def next(self):
return self.readline()
class Filer(object):
def __init__(self, directory):
self.basedir = self._ensure_path(directory)
self.tmp = self._ensure_path(pth.join(self.basedir, "tmp"))
self.incoming = self._ensure_path(pth.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 pth.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.time(), device, inode)
def create_unique_file(self):
# First find and open name unique within tmp
tmpname = None
while not tmpname:
tmpname = self._get_new_name()
try:
fd = os.open(pth.join(self.tmp, tmpname), os.O_CREAT | os.O_RDWR | os.O_EXCL)
except OSError as e:
if e.errno != errno.EEXIST:
raise # other errors than duplicates should get noticed
tmpname = None
# Now we know the device/inode, rename to make unique within system
newname = self._get_new_name(fd)
os.rename(pth.join(self.tmp, tmpname), pth.join(self.tmp, newname))
nf = os.fdopen(fd, "w")
return nf, newname
def publish_file(self, short_name):
os.rename(pth.join(self.tmp, short_name), pth.join(self.incoming, short_name))
class IdeaGen(object):
idea_categories = {'Intrusion.Botnet': re.compile("A Network Trojan was detected, signature: ET CNC"),
'Malware.Trojan': re.compile("A Network Trojan was detected, signature: ET (?!CNC)"),
'Recon.Scanning': re.compile("signature: (ET|GPL) (SCAN|DNS Query|CURRENT_EVENTS DNS Query)"),
'Availability.DoS': re.compile("signature: ET DOS"),
'Abusive.Spam': re.compile("signature:.*?Spam(?!cop.net)"),
'Attempt.Exploit': re.compile("category: Web Application Attack")}
other_a_re = re.compile("signature: (GPL SNMP|GPL WEB_SERVER|ET DROP Dshield|ET WEB_CLIENT Hex Obfuscation|GPL TELNET)")
other_b_re = re.compile("Potential Corporate Privacy Violation, signature: ET (?!POLICY)")
def __init__(self, name, test):
self.name = name
self.test = test
def convert_category(self, category, signature):
if not (category and signature):
return None
suricata_category = "category: " + category + ", signature: " + signature
if IdeaGen.other_a_re.search(suricata_category) or IdeaGen.other_b_re.search(suricata_category):
return "Other"
for category, pattern in IdeaGen.idea_categories.items():
if pattern.search(suricata_category):
return category
return None
def gen_event_idea(self, timestamp, category, src_ip, src_port, proto, dest_ip, dest_port, orig_data,
incident_desription):
event = {
'Format': "IDEA0",
'ID': str(uuid4()),
'DetectTime': timestamp,
'Category': [category] + (["Test"] if self.test else []),
'Note': incident_desription
}
source = {}
target = {}
if src_ip:
af = "IP4" if not ':' in src_ip else "IP6"
source[af] = [src_ip]
if src_port:
source['Port'] = [src_port]
if proto:
source['Proto'] = [proto]
if dest_ip:
af = "IP4" if not ':' in dest_ip else "IP6"
target[af] = [dest_ip]
if dest_port:
target['Port'] = [dest_port]
if source:
event['Source'] = [source]
if target:
event['Target'] = [target]
if orig_data:
attachment = {'Handle': "att1",
'Note': "original data",
'Content': orig_data}
event['Attach'] = attachment
event['Node'] = [{
'Name': self.name,
'Type': ["Connection", "Honeypot", "Recon"],
'SW': ["HP Tipping Point"],
}]
return event
def daemonize(
work_dir=None, chroot_dir=None,
umask=None, uid=None, gid=None,
pidfile=None, files_preserve=[], signals={}):
# Dirs, limits, users
if chroot_dir is not None:
os.chdir(chroot_dir)
os.chroot(chroot_dir)
if umask is not None:
os.umask(umask)
if work_dir is not None:
os.chdir(work_dir)
if gid is not None:
os.setgid(gid)
if uid is not None:
os.setuid(uid)
# Doublefork, split session
if os.fork() > 0:
os._exit(0)
try:
os.setsid()
except OSError:
pass
if os.fork() > 0:
os._exit(0)
# Setup signal handlers
for (signum, handler) in signals.items():
signal.signal(signum, handler)
# Close descriptors
descr_preserve = set(f.fileno() for f in files_preserve)
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
if maxfd == resource.RLIM_INFINITY:
maxfd = 65535
for fd in range(maxfd, 3, -1): # 3 means omit stdin, stdout, stderr
if fd not in descr_preserve:
try:
os.close(fd)
except Exception:
pass
# Redirect stdin, stdout, stderr to /dev/null
devnull = os.open(os.devnull, os.O_RDWR)
for fd in range(3):
os.dup2(devnull, fd)
# PID file
if pidfile is not None:
pidd = os.open(pidfile, os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_TRUNC)
os.write(pidd, str(os.getpid())+"\n")
os.close(pidd)
# Define and setup atexit closure
@atexit.register
def unlink_pid():
try:
os.unlink(pidfile)
except Exception:
pass
def get_args():
optp = optparse.OptionParser(
usage="usage: %prog [options] logfile ...",
description="Watch Suricata logfiles and generate Idea events into directory")
optp.add_option(
"-n", "--name",
default=None,
dest="name",
type="string",
action="store",
help="Warden client name")
optp.add_option(
"--test",
default=False,
dest="test",
action="store_true",
help="Add Test category")
optp.add_option(
"-o", "--oneshot",
default=False,
dest="oneshot",
action="store_true",
help="process files and quit (do not daemonize)")
optp.add_option(
"--poll",
default=1,
dest="poll",
type="int",
action="store",
help="log file polling interval")
optp.add_option(
"-d", "--dir",
default=None,
dest="dir",
type="string",
action="store",
help="Target directory (mandatory)")
optp.add_option(
"-p", "--pid",
default=pth.join("/var/run", pth.splitext(pth.basename(sys.argv[0]))[0] + ".pid"),
dest="pid",
type="string",
action="store",
help="create PID file with this name (default: %default)")
optp.add_option(
"-u", "--uid",
default=None,
dest="uid",
type="int",
action="store",
help="user id to run under")
optp.add_option(
"-g", "--gid",
default=None,
dest="gid",
type="int",
action="store",
help="group id to run under")
optp.add_option(
"--origdata",
default=False,
dest="origdata",
action="store_true",
help="Store original report to IDEA message")
return optp
def save_events(event, filer):
f, name = filer.create_unique_file()
with f:
f.write(json.dumps(event, ensure_ascii=True))
filer.publish_file(name)
running_flag = True
reload_flag = False
def terminate_me(signum, frame):
global running_flag
running_flag = False
def reload_me(signum, frame):
global reload_flag
reload_flag = True
def main():
global reload_flag
global running_flag
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG, filename='suricataDaemonLog.log', filemode='w')
optp = get_args()
# win
'''opts, args = optp.parse_args(["--origdata", "-d", "C:\\Users\\Aisik\\PycharmProjects\\SuricataToIdea\\IdeaLogTest.txt", "-n",
"cz.cesnet.server.suricata", "C:\\Users\\Aisik\\PycharmProjects\\SuricataToIdea\\"
"var\\log\\suricata\\suricataJsonLog.txt"])'''
# linux
opts, args = optp.parse_args(["--origdata", "-d", "/root/Dokumenty/PycharmProjects/SuricataToIdea/IdeaLogTest", "-n",
"cz.cesnet.server.suricata", "/root/Dokumenty/PycharmProjects/SuricataToIdea/"
"var/log/suricata/suricataJsonLog.txt"])
if not args or opts.name is None or opts.dir is None:
optp.print_help()
sys.exit()
if opts.oneshot:
signal.signal(signal.SIGINT, terminate_me)
signal.signal(signal.SIGTERM, terminate_me)
files = [open(arg) for arg in args]
else:
daemonize(
pidfile=opts.pid,
uid=opts.uid,
gid=opts.gid,
signals={
signal.SIGINT: terminate_me,
signal.SIGTERM: terminate_me,
signal.SIGHUP: reload_me
})
files = [FileWatcher(arg) for arg in args]
filer = Filer(opts.dir)
idea_gen = IdeaGen(opts.name, opts.test)
while running_flag:
for log_file in files:
while True:
try:
line = log_file.readline()
if line is None or not line.strip():
logging.info("no line")
break
log = json.loads(line)
logging.info("readline")
category = idea_gen.convert_category(category=log.get('alert').get('category'),
signature=log.get('alert').get('signature'))
if category and log.get('timestamp'):
idea_event = idea_gen.gen_event_idea(timestamp=log['timestamp'], category=category,
src_ip=log.get('src_ip'),
src_port=log.get('src_port'), proto=log.get('proto'),
dest_ip=log.get('dest_ip'),
dest_port=log.get('dest_port'),
orig_data=str(log) if opts.origdata else False,
incident_desription=log['alert']['signature'])
save_events(idea_event, filer)
except Exception as e:
logging.debug(e)
if not running_flag:
break
if reload_flag:
for f in files:
f.close()
f.open()
reload_flag = False
if opts.oneshot:
break
else:
time.sleep(opts.poll)
if __name__ == "__main__":
main()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment