Merge branch 'devel-sigmac-config' into devel-sigmac

This commit is contained in:
Thomas Patzke 2017-03-06 22:50:32 +01:00
commit aaa3057769
3 changed files with 114 additions and 8 deletions

View File

@ -22,6 +22,11 @@ class BaseBackend:
identifier = "base"
active = False
def __init__(self, sigmaconfig):
if not isinstance(sigmaconfig, (sigma.SigmaConfiguration, None)):
raise TypeError("SigmaConfiguration object expected")
self.sigmaconfig = sigmaconfig
def generate(self, parsed):
return self.generateNode(parsed.getParseTree())
@ -96,7 +101,7 @@ class ElasticsearchQuerystringBackend(BaseBackend):
key, value = node
if type(value) not in (str, int, list):
raise TypeError("Map values must be strings, numbers or lists, not " + str(type(value)))
return "%s:%s" % (key, self.generateNode(value))
return "%s:%s" % (self.sigmaconfig.get_fieldmapping(key), self.generateNode(value))
def generateValueNode(self, node):
return "\"%s\"" % (self.cleanValue(str(node)))
@ -140,11 +145,53 @@ class SplunkBackend(BaseBackend):
def generateMapItemNode(self, node):
key, value = node
if type(value) in (str, int):
return '%s=%s' % (key, self.generateNode(value))
return '%s=%s' % (self.sigmaconfig.get_fieldmapping(key), self.generateNode(value))
elif type(value) == list:
return "(" + (" OR ".join(['%s=%s' % (key, self.generateValueNode(item)) for item in value])) + ")"
return "(" + (" OR ".join(['%s=%s' % (self.sigmaconfig.get_fieldmapping(key), self.generateValueNode(item)) for item in value])) + ")"
else:
raise TypeError("Map values must be strings, numbers or lists, not " + str(type(value)))
def generateValueNode(self, node):
return "\"%s\"" % (self.cleanValue(str(node)))
class FieldnameListBackend(BaseBackend):
"""List all fieldnames from given Sigma rules for creation of a field mapping configuration."""
identifier = "fieldlist"
active = True
def generate(self, parsed):
return "\n".join(sorted(set(list(flatten(self.generateNode(parsed.getParseTree()))))))
def generateANDNode(self, node):
return [self.generateNode(val) for val in node]
def generateORNode(self, node):
return self.generateANDNode(node)
def generateNOTNode(self, node):
return self.generateNode(node.item)
def generateSubexpressionNode(self, node):
return self.generateNode(node.items)
def generateListNode(self, node):
if not set([type(value) for value in node]).issubset({str, int}):
raise TypeError("List values must be strings or numbers")
return [self.generateNode(value) for value in node]
def generateMapItemNode(self, node):
key, value = node
if type(value) not in (str, int, list):
raise TypeError("Map values must be strings, numbers or lists, not " + str(type(value)))
return [self.sigmaconfig.get_fieldmapping(key)]
def generateValueNode(self, node):
return []
# Helpers
def flatten(l):
for i in l:
if type(i) == list:
yield from flatten(i)
else:
yield i

View File

@ -192,7 +192,7 @@ class SigmaConditionTokenizer:
def index(self, item):
return self.tokens.index(item)
class SigmaParseError(Exception):
pass
@ -349,3 +349,52 @@ class SigmaConditionParser:
def getParseTree(self):
return(self.parsedSearch[0])
# Configuration
class SigmaConfiguration:
"""Sigma converter configuration. Contains field mappings and logsource descriptions"""
def __init__(self, configyaml=None):
if configyaml == None:
self.fieldmappings = dict()
self.logsources = dict()
else:
config = yaml.safe_load(configyaml)
try:
self.fieldmappings = config['fieldmappings']
except KeyError:
self.fieldmappings = dict()
if type(self.fieldmappings) != dict:
raise SigmaConfigParseError("Fieldmappings must be a map")
try:
self.logsources = config['logsources']
except KeyError:
self.logsources = dict()
if type(self.logsources) != dict:
raise SigmaConfigParseError("Logsources must be a map")
for name, logsource in self.logsources.items():
if type(logsource) != dict:
raise SigmaConfigParseError("Logsource definitions must be maps")
if 'category' in logsource and type(logsource['category']) != str \
or 'product' in logsource and type(logsource['product']) != str \
or 'service' in logsource and type(logsource['service']) != str:
raise SigmaConfigParseError("Logsource category, product or service must be a string")
if 'index' in logsource:
if type(logsource['index']) not in (str, list):
raise SigmaConfigParseError("Logsource index must be string or list of strings")
if type(logsource['index']) == list and not set([type(index) for index in logsource['index']]).issubset({str}):
raise SigmaConfigParseError("Logsource index patterns must be strings")
if 'conditions' in logsource and type(logsource['conditions']) != dict:
raise SigmaConfigParseError("Logsource conditions must be a map")
def get_fieldmapping(self, fieldname):
"""Return mapped fieldname if mapping defined or field name given in parameter value"""
try:
return self.fieldmappings[fieldname]
except KeyError:
return fieldname
class SigmaConfigParseError(Exception):
pass

View File

@ -7,7 +7,7 @@ import yaml
import json
import pathlib
import itertools
from sigma import SigmaParser, SigmaParseError
from sigma import SigmaParser, SigmaParseError, SigmaConfiguration, SigmaConfigParseError
import backends
def print_verbose(*args, **kwargs):
@ -50,12 +50,22 @@ if cmdargs.target_list:
if cmdargs.output:
print("--output/-o not yet implemented", file=sys.stderr)
sys.exit(99)
sigmaconfig = SigmaConfiguration()
if cmdargs.config:
print("--config/-c not yet implemented", file=sys.stderr)
sys.exit(99)
try:
conffile = cmdargs.config
f = open(conffile)
sigmaconfig = SigmaConfiguration(f)
except OSError as e:
print("Failed to open Sigma configuration file %s: %s" % (conffile, str(e)))
except yaml.parser.ParserError as e:
print("Sigma configuration file %s is no valid YAML: %s" % (conffile, str(e)))
except SigmaParseError as e:
print("Sigma configuration parse error in %s: %s" % (conffile, str(e)))
try:
backend = backends.getBackend(cmdargs.target)()
backend = backends.getBackend(cmdargs.target)(sigmaconfig)
except LookupError as e:
print("Backend not found!")
sys.exit(1)