Files
blue-team-tools/tools/sigma/backends/carbonblack.py
T

267 lines
9.1 KiB
Python
Raw Normal View History

2020-02-24 19:29:45 +02:00
import re
2020-05-08 13:41:52 +03:00
import requests
import json
import os
from sigma.config.eventdict import event
2020-02-24 19:29:45 +02:00
from fnmatch import fnmatch
from sigma.backends.base import SingleTextQueryBackend
from sigma.backends.exceptions import NotSupportedError
from sigma.parser.modifiers.type import SigmaRegularExpressionModifier
from sigma.parser.condition import ConditionOR, ConditionAND, NodeSubexpression
from sigma.parser.modifiers.base import SigmaTypeModifier
2020-08-18 15:57:13 +07:00
from sigma.parser.modifiers.type import SigmaRegularExpressionModifier
2020-02-24 19:29:45 +02:00
class CarbonBlackWildcardHandlingMixin:
"""
Determine field mapping to keyword subfields depending on existence of wildcards in search values. Further,
provide configurability with backend parameters.
"""
2020-08-18 15:57:13 +07:00
2020-02-24 19:29:45 +02:00
# options = SingleTextQueryBackend.options + (
# ("keyword_field", None, "Keyword sub-field name", None),
# ("keyword_blacklist", None, "Fields that don't have a keyword subfield (wildcards * and ? allowed)", None)
# )
reContainsWildcard = re.compile("(?:(?<!\\\\)|\\\\\\\\)[*?]").search
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.matchKeyword = True
try:
self.blacklist = self.keyword_blacklist.split(",")
except AttributeError:
self.blacklist = list()
def containsWildcard(self, value):
"""Determine if value contains wildcard."""
if type(value) == str:
res = self.reContainsWildcard(value)
return res
else:
return False
class CarbonBlackQueryBackend(CarbonBlackWildcardHandlingMixin, SingleTextQueryBackend):
"""Converts Sigma rule into CarbonBlack query string. Only searches, no aggregations."""
2020-08-18 15:57:13 +07:00
2020-02-24 19:29:45 +02:00
identifier = "carbonblack"
active = True
2020-08-18 15:57:13 +07:00
# reEscape = re.compile("([\s+\\-=!(){}\\[\\]^\"~:/]|(?<!\\\\)\\\\(?![*?\\\\])|\\\\u|&&|\\|\\|)")
2020-05-08 13:41:52 +03:00
reEscape = re.compile("([\s\s+()\"])")
2020-02-24 19:29:45 +02:00
reClear = re.compile("[<>]")
andToken = " AND "
orToken = " OR "
notToken = " -"
subExpression = "(%s)"
listExpression = "(%s)"
2020-02-24 19:29:45 +02:00
listSeparator = " OR "
valueExpression = '%s'
2020-08-18 15:57:13 +07:00
typedValueExpression = {SigmaRegularExpressionModifier: "/%s/"}
2020-02-24 19:29:45 +02:00
nullExpression = "NOT _exists_:%s"
notNullExpression = "_exists_:%s"
mapExpression = "%s:%s"
mapListsSpecialHandling = False
2020-08-18 15:57:13 +07:00
escapeCharacters = [
'\\',
'"',
'\'',
'(',
')',
'[',
']',
'{',
'}',
',',
'=',
'<',
'>',
'&',
'|',
';',
':',
]
2020-02-24 19:29:45 +02:00
def __init__(self, *args, **kwargs):
"""Initialize field mappings."""
super().__init__(*args, **kwargs)
self.category = None
self.excluded_fields = None
2020-08-18 15:57:13 +07:00
def cleanLeading(self, val):
if val.startswith("*"):
val = val[1:]
if val.endswith("*"):
val = val[:-1]
return val.strip()
def escapeCharacter(self, val):
for ch in self.escapeCharacters:
val = val.replace(ch, '\\' + ch)
return val
def unescapeCharacter(self, val):
for ch in self.escapeCharacters:
val = val.replace('\\' + ch, ch)
return val
def cleanWhitespace(self, val):
val = val.replace('*', ' AND ').replace(' ', ' ')
if re.match('\S+ \S', val):
matchs = re.findall('(?:^|\(| )(.+?)(?:\)| OR| AND|$)', val)
for strMatch in matchs:
if re.match('\S+ \S', strMatch):
strUnescapeMatch = self.unescapeCharacter(strMatch)
val = val.replace(strMatch, '"{}"'.format(strUnescapeMatch))
return val.strip()
2020-02-24 19:29:45 +02:00
def cleanValue(self, val):
2020-08-18 15:57:13 +07:00
if "[1 to *]" in val:
2020-05-08 13:41:52 +03:00
self.reEscape = re.compile("([()])")
2020-08-18 15:57:13 +07:00
val = super().cleanValue(val)
val = val.strip()
# else:
# self.reEscape = re.compile("([\s\s+()])")
elif isinstance(val, str):
val = val.strip()
val = self.cleanLeading(val)
val = self.escapeCharacter(val)
val = self.cleanWhitespace(val)
2020-02-24 19:29:45 +02:00
return val
2020-08-18 15:57:13 +07:00
def cleanIPRange(self, value):
2020-05-08 13:41:52 +03:00
new_value = value
2020-08-18 15:57:13 +07:00
if type(new_value) is str and value.find('*'):
sub = value.count('.')
if value[-2:] == '.*':
value = value[:-2]
min_ip = value + '.0' * (4 - sub)
new_value = min_ip + '/' + str(8 * (4 - sub))
2020-05-08 13:41:52 +03:00
elif type(new_value) is list:
for index, vl in enumerate(new_value):
new_value[index] = self.cleanIPRange(vl)
return new_value
2020-02-24 19:29:45 +02:00
def generateValueNode(self, node):
2020-05-08 13:41:52 +03:00
result = self.valueExpression % (str(node))
2020-02-24 19:29:45 +02:00
if result == "" or result.isspace():
return '""'
else:
2020-08-18 15:57:13 +07:00
if self.matchKeyword: # don't quote search value on keyword field
2020-02-24 19:29:45 +02:00
return result
else:
return "%s" % result
def generateMapItemNode(self, node):
fieldname, value = node
2020-08-18 15:57:13 +07:00
if fieldname == "EventID" and (type(value) is str or type(value) is int):
2020-05-08 13:41:52 +03:00
fieldname = self.generateEventKey(value)
value = self.generateEventValue(value)
2020-02-24 19:29:45 +02:00
if fieldname.lower() in self.excluded_fields:
return
else:
transformed_fieldname = self.fieldNameMapping(fieldname, value)
2020-08-18 15:57:13 +07:00
if transformed_fieldname == "ipaddr":
2020-05-08 13:41:52 +03:00
value = self.cleanIPRange(value)
2020-08-18 15:57:13 +07:00
if (
self.mapListsSpecialHandling == False
and type(value) in (str, int, list)
or self.mapListsSpecialHandling == True
and type(value) in (str, int)
):
# return self.mapExpression % (transformed_fieldname, self.generateNode(value))
2020-02-24 19:29:45 +02:00
if isinstance(value, list):
2020-08-18 15:57:13 +07:00
return self.generateNode(
[
self.mapExpression
% (transformed_fieldname, self.cleanValue(item))
for item in value
]
)
2020-02-24 19:29:45 +02:00
elif isinstance(value, str) or isinstance(value, int):
2020-08-18 15:57:13 +07:00
return self.mapExpression % (
transformed_fieldname,
self.generateNode(self.cleanValue(value)),
)
2020-02-24 19:29:45 +02:00
elif type(value) == list:
return self.generateMapItemListNode(transformed_fieldname, value)
2020-08-18 15:57:13 +07:00
elif isinstance(value, SigmaTypeModifier) and not isinstance(
value, SigmaRegularExpressionModifier
):
2020-02-24 19:29:45 +02:00
return self.generateMapItemTypedNode(transformed_fieldname, value)
elif value is None:
return self.nullExpression % (transformed_fieldname,)
else:
2020-08-18 15:57:13 +07:00
raise TypeError(
"Backend does not support map values of type " + str(type(value))
)
2020-02-24 19:29:45 +02:00
def generateNOTNode(self, node):
expression = super().generateNode(node.item)
if expression:
return "(%s%s)" % (self.notToken, expression)
2020-08-18 15:57:13 +07:00
# Function to upload watchlists through CB API
def postAPI(self, result, title, desc):
2020-05-08 13:41:52 +03:00
url = os.getenv("cbapi_watchlist")
body = {
2020-08-18 15:57:13 +07:00
"name": title,
"search_query": "q=" + str(result),
"description": desc,
"index_type": "events",
2020-05-08 13:41:52 +03:00
}
2020-08-18 15:57:13 +07:00
header = {"X-Auth-Token": os.getenv("APIToken")}
2020-05-08 13:41:52 +03:00
print(title)
2020-08-18 15:57:13 +07:00
x = requests.post(url, data=json.dumps(body), headers=header, verify=False)
2020-05-08 13:41:52 +03:00
print(x.text)
def generateEventKey(self, value):
2020-08-18 15:57:13 +07:00
if value in event:
2020-05-08 13:41:52 +03:00
return event[value][0]
else:
return 'eventid'
def generateEventValue(self, value):
2020-08-18 15:57:13 +07:00
if value in event:
2020-05-08 13:41:52 +03:00
return event[value][1]
else:
return ''
2020-02-24 19:29:45 +02:00
def generate(self, sigmaparser):
"""Method is called for each sigma rule and receives the parsed rule (SigmaParser)"""
2020-05-08 13:41:52 +03:00
title = sigmaparser.parsedyaml["title"]
desc = sigmaparser.parsedyaml["description"]
2020-02-24 19:29:45 +02:00
try:
2020-08-18 15:57:13 +07:00
self.category = sigmaparser.parsedyaml['logsource'].setdefault(
'category', None
)
2020-02-24 19:29:45 +02:00
self.counted = sigmaparser.parsedyaml.get('counted', None)
2020-08-18 15:57:13 +07:00
self.excluded_fields = [
item.lower()
for item in sigmaparser.config.config.get("excludedfields", [])
]
2020-02-24 19:29:45 +02:00
except KeyError:
self.category = None
2020-05-08 13:41:52 +03:00
for parsed in sigmaparser.condparsed:
query = self.generateQuery(parsed)
result = ""
if query is not None:
result += query
# self.postAPI(result,title,desc)
return result
# if self.category == "process_creation":
# for parsed in sigmaparser.condparsed:
# query = self.generateQuery(parsed)
# result = ""
# if query is not None:
# result += query
# return result
# else:
# raise NotSupportedError("Not supported logsource category.")