2020-07-07 12:04:16 +00:00
|
|
|
import sigma
|
|
|
|
from sigma.parser.modifiers.base import SigmaTypeModifier
|
|
|
|
from sigma.parser.modifiers.type import SigmaRegularExpressionModifier
|
|
|
|
from .base import SingleTextQueryBackend
|
|
|
|
|
|
|
|
|
|
|
|
class STIXBackend(SingleTextQueryBackend):
|
|
|
|
"""Converts Sigma rule into STIX pattern."""
|
|
|
|
identifier = "stix"
|
|
|
|
active = True
|
|
|
|
andToken = " AND "
|
|
|
|
orToken = " OR "
|
|
|
|
notToken = "NOT "
|
|
|
|
subExpression = "(%s)"
|
|
|
|
valueExpression = "\'%s\'"
|
|
|
|
mapExpression = "%s = %s"
|
2020-07-23 15:47:55 +00:00
|
|
|
notMapExpression = "%s != %s"
|
2020-07-07 12:04:16 +00:00
|
|
|
mapListsSpecialHandling = True
|
2020-07-08 11:09:26 +00:00
|
|
|
sigmaSTIXObjectName = "x-sigma"
|
2020-07-07 12:04:16 +00:00
|
|
|
|
|
|
|
def cleanKey(self, key):
|
|
|
|
if key is None:
|
|
|
|
raise TypeError("Backend does not support empty key " + str(key))
|
|
|
|
else:
|
|
|
|
return key
|
|
|
|
|
|
|
|
def cleanValue(self, value):
|
|
|
|
return value
|
|
|
|
|
2020-07-23 14:13:30 +00:00
|
|
|
def generateANDNode(self, node, currently_within_NOT_node=False):
|
|
|
|
generated = [self.generateNode(val, currently_within_NOT_node) for val in node]
|
|
|
|
filtered = [g for g in generated if g is not None]
|
|
|
|
if filtered:
|
|
|
|
if self.sort_condition_lists:
|
|
|
|
filtered = sorted(filtered)
|
|
|
|
return self.andToken.join(filtered)
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2020-07-23 14:18:16 +00:00
|
|
|
def generateORNode(self, node, currently_within_NOT_node=False):
|
2020-07-23 14:13:30 +00:00
|
|
|
generated = [self.generateNode(val, currently_within_NOT_node) for val in node]
|
|
|
|
filtered = [g for g in generated if g is not None]
|
|
|
|
if filtered:
|
|
|
|
if self.sort_condition_lists:
|
|
|
|
filtered = sorted(filtered)
|
|
|
|
return self.orToken.join(filtered)
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2020-07-23 14:18:16 +00:00
|
|
|
def generateNOTNode(self, node, currently_within_NOT_node=False):
|
2020-07-23 15:47:55 +00:00
|
|
|
currently_within_NOT_node = not(currently_within_NOT_node)
|
2020-07-23 14:13:30 +00:00
|
|
|
generated = self.generateNode(node.item, currently_within_NOT_node)
|
|
|
|
if generated is not None:
|
|
|
|
return generated
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2020-07-23 14:18:16 +00:00
|
|
|
def generateSubexpressionNode(self, node, currently_within_NOT_node=False):
|
2020-07-23 14:13:30 +00:00
|
|
|
generated = self.generateNode(node.items, currently_within_NOT_node)
|
|
|
|
if generated:
|
|
|
|
return self.subExpression % generated
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
2020-07-23 14:18:16 +00:00
|
|
|
def generateMapItemNode(self, node, currently_within_NOT_node=False):
|
2020-07-23 14:13:30 +00:00
|
|
|
fieldname, value = node
|
|
|
|
|
|
|
|
transformed_fieldname = self.fieldNameMapping(fieldname, value)
|
|
|
|
if self.mapListsSpecialHandling == False and type(value) in (str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int):
|
2020-07-23 15:47:55 +00:00
|
|
|
if currently_within_NOT_node:
|
|
|
|
return self.notMapExpression % (transformed_fieldname, self.generateNode(value))
|
2020-07-23 14:13:30 +00:00
|
|
|
return self.mapExpression % (transformed_fieldname, self.generateNode(value))
|
|
|
|
elif type(value) == list:
|
|
|
|
return self.generateMapItemListNode(transformed_fieldname, value, currently_within_NOT_node)
|
|
|
|
elif isinstance(value, SigmaTypeModifier):
|
|
|
|
return self.generateMapItemTypedNode(transformed_fieldname, value)
|
|
|
|
elif value is None:
|
|
|
|
return self.nullExpression % (transformed_fieldname, )
|
|
|
|
else:
|
|
|
|
raise TypeError("Backend does not support map values of type " + str(type(value)))
|
|
|
|
|
2020-07-23 14:18:16 +00:00
|
|
|
def generateMapItemListNode(self, key, value, currently_within_NOT_node=False):
|
2020-07-07 12:04:16 +00:00
|
|
|
items_list = list()
|
|
|
|
for item in value:
|
|
|
|
if type(item) == str and "*" in item:
|
|
|
|
item = item.replace("*", "%")
|
2020-07-23 13:41:33 +00:00
|
|
|
if currently_within_NOT_node:
|
|
|
|
items_list.append('%s NOT LIKE %s' % (self.cleanKey(key), self.generateValueNode(item)))
|
|
|
|
else:
|
|
|
|
items_list.append('%s LIKE %s' % (self.cleanKey(key), self.generateValueNode(item)))
|
2020-07-07 12:04:16 +00:00
|
|
|
else:
|
2020-07-23 13:41:33 +00:00
|
|
|
if currently_within_NOT_node:
|
|
|
|
items_list.append('%s != %s' % (self.cleanKey(key), self.generateValueNode(item)))
|
|
|
|
else:
|
|
|
|
items_list.append('%s = %s' % (self.cleanKey(key), self.generateValueNode(item)))
|
|
|
|
if currently_within_NOT_node:
|
|
|
|
return '(' + " AND ".join(items_list) + ')'
|
|
|
|
else:
|
|
|
|
return '('+" OR ".join(items_list)+')'
|
2020-07-07 12:04:16 +00:00
|
|
|
|
|
|
|
def generateMapItemTypedNode(self, key, value):
|
|
|
|
if type(value) == SigmaRegularExpressionModifier:
|
|
|
|
regex = str(value)
|
|
|
|
# Regular Expressions have to match the full value in QRadar
|
|
|
|
if not (regex.startswith('^') or regex.startswith('.*')):
|
|
|
|
regex = '.*' + regex
|
|
|
|
if not (regex.endswith('$') or regex.endswith('.*')):
|
|
|
|
regex = regex + '.*'
|
|
|
|
return "%s MATCHES %s" % (self.cleanKey(key), self.generateValueNode(regex))
|
|
|
|
else:
|
|
|
|
raise NotImplementedError("Type modifier '{}' is not supported by backend".format(value.identifier))
|
|
|
|
|
2020-07-23 14:18:16 +00:00
|
|
|
def generateMapItemNode(self, node, currently_within_NOT_node=False):
|
2020-07-07 12:04:16 +00:00
|
|
|
key, value = node
|
|
|
|
if ":" not in key:
|
2020-07-08 11:09:26 +00:00
|
|
|
key = "%s:%s" % (self.sigmaSTIXObjectName, str(key).lower())
|
2020-07-07 12:04:16 +00:00
|
|
|
if self.mapListsSpecialHandling == False and type(value) in (str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int):
|
|
|
|
if type(value) == str and "*" in value:
|
|
|
|
value = value.replace("*", "%")
|
2020-07-23 13:41:33 +00:00
|
|
|
if currently_within_NOT_node:
|
|
|
|
return "%s NOT LIKE %s" % (self.cleanKey(key), self.generateValueNode(value))
|
2020-07-07 12:04:16 +00:00
|
|
|
return "%s LIKE %s" % (self.cleanKey(key), self.generateValueNode(value))
|
|
|
|
elif type(value) in (str, int):
|
2020-07-23 15:47:55 +00:00
|
|
|
if currently_within_NOT_node:
|
|
|
|
return self.notMapExpression % (self.cleanKey(key), self.generateValueNode(value))
|
2020-07-07 12:04:16 +00:00
|
|
|
return self.mapExpression % (self.cleanKey(key), self.generateValueNode(value))
|
|
|
|
elif type(value) == list:
|
2020-07-23 13:41:33 +00:00
|
|
|
return self.generateMapItemListNode(key, value, currently_within_NOT_node)
|
2020-07-07 12:04:16 +00:00
|
|
|
elif isinstance(value, SigmaTypeModifier):
|
|
|
|
return self.generateMapItemTypedNode(key, value)
|
|
|
|
else:
|
|
|
|
raise TypeError("Backend does not support map values of type " + str(type(value)))
|
|
|
|
|
2020-07-28 15:52:02 +00:00
|
|
|
def generateValueNode(self, node, keypresent=True):
|
|
|
|
if keypresent == False:
|
|
|
|
if type(node) == str and "*" in node:
|
|
|
|
node = node.replace("*", "%")
|
|
|
|
return "artifact:payload_bin LIKE \'{0}\'".format(self.cleanValue(str(node)))
|
|
|
|
else:
|
|
|
|
return self.valueExpression % (self.cleanValue(str(node)))
|
2020-07-07 12:04:16 +00:00
|
|
|
|
2020-07-23 13:41:33 +00:00
|
|
|
def generateNode(self, node, currently_within_NOT_node=False):
|
2020-07-07 12:04:16 +00:00
|
|
|
if type(node) == sigma.parser.condition.ConditionAND:
|
2020-07-23 13:41:33 +00:00
|
|
|
if currently_within_NOT_node:
|
|
|
|
return self.generateORNode(node, currently_within_NOT_node)
|
|
|
|
return self.generateANDNode(node, currently_within_NOT_node)
|
2020-07-07 12:04:16 +00:00
|
|
|
elif type(node) == sigma.parser.condition.ConditionOR:
|
2020-07-23 13:41:33 +00:00
|
|
|
if currently_within_NOT_node:
|
|
|
|
return self.generateANDNode(node, currently_within_NOT_node)
|
|
|
|
return self.generateORNode(node, currently_within_NOT_node)
|
2020-07-07 12:04:16 +00:00
|
|
|
elif type(node) == sigma.parser.condition.ConditionNOT:
|
2020-07-23 13:41:33 +00:00
|
|
|
return self.generateNOTNode(node, currently_within_NOT_node)
|
2020-07-07 12:04:16 +00:00
|
|
|
elif type(node) == sigma.parser.condition.NodeSubexpression:
|
2020-07-23 13:41:33 +00:00
|
|
|
return self.generateSubexpressionNode(node, currently_within_NOT_node)
|
2020-07-07 12:04:16 +00:00
|
|
|
elif type(node) == tuple:
|
2020-07-23 13:41:33 +00:00
|
|
|
return self.generateMapItemNode(node, currently_within_NOT_node)
|
2020-07-28 15:52:02 +00:00
|
|
|
elif type(node) in (str, int):
|
|
|
|
return self.generateValueNode(node, keypresent=False)
|
2020-07-07 12:04:16 +00:00
|
|
|
else:
|
|
|
|
raise TypeError("Node type %s was not expected in Sigma parse tree" % (str(type(node))))
|
|
|
|
|
|
|
|
def generate(self, sigmaparser):
|
|
|
|
for parsed in sigmaparser.condparsed:
|
|
|
|
query = self.generateQuery(parsed, sigmaparser)
|
|
|
|
return "[" + query + "]"
|
|
|
|
|
|
|
|
def generateQuery(self, parsed, sigmaparser):
|
|
|
|
result = self.generateNode(parsed.parsedSearch)
|
|
|
|
return result
|