Added ArcSight & Qualys backends

This commit is contained in:
nikotin
2018-06-07 16:18:23 +03:00
parent 9640806678
commit d13e8d7bd3
4 changed files with 358 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
logsources:
linux:
product: linux
conditions:
deviceVendor: Unix
linux-sshd:
product: linux
service: sshd
conditions:
deviceVendor: Unix
linux-auth:
product: linux
service: auth
conditions:
deviceVendor: Unix
linux-clamav:
product: linux
service: clamav
conditions:
deviceVendor: Unix
windows-dns:
product: windows
service: dns-server
conditions:
deviceVendor: Microsoft
deviceProduct: DNS-Server
windows-pc:
product: windows
service: powershell-classic
conditions:
deviceVendor: Microsoft
windows-sys:
product: windows
service: sysmon
conditions:
deviceVendor: Microsoft
deviceProduct: Sysmon
windows-sec:
product: windows
service: security
conditions:
deviceVendor: Microsoft
deviceProduct: Microsoft Windows
windows-power:
product: windows
service: powershell
conditions:
deviceVendor: Microsoft
windows-system:
product: windows
service: system
conditions:
deviceVendor: Microsoft
windows-driver:
product: windows
service: driver-framework
conditions:
deviceVendor: Microsoft
windows-app:
product: windows
service: application
conditions:
deviceVendor: Microsoft
proxy:
category: proxy
conditions:
categoryDeviceGroup: /Proxy
python:
product: python
conditions:
deviceProduct: Python
categoryDeviceGroup: /Application
ruby_on_rails:
product: ruby_on_rails
conditions:
deviceProduct: Ruby on Rails
categoryDeviceGroup: /Application
spring:
product: spring
conditions:
deviceProduct: Spring
categoryDeviceGroup: /Application
apache:
product: apache
conditions:
deviceProduct: Apache
categoryDeviceGroup: /Application
firewall:
product: firewall
conditions:
categoryDeviceGroup: /Firewall
fieldmappings:
EventID: externalId
dst:
- destinationAddress
dst_ip:
- destinationAddress
src:
- sourceAddress
src_ip:
- sourceAddress
+17
View File
@@ -0,0 +1,17 @@
fieldmappings:
dst:
- network.remote.address.ip
dst_ip:
- network.remote.address.ip
src:
- network.local.address.ip
src_ip:
- network.local.address.ip
file_hash:
- file.hash.md5
- file.hash.sha256
NewProcessName: process.name
ServiceName: process.name
ServiceFileName: process.name
TargetObject: registry.path
+229
View File
@@ -982,3 +982,232 @@ class BackendError(Exception):
class NotSupportedError(BackendError):
"""Exception is raised if some output is required that is not supported by the target language."""
pass
class PartialMatchError(Exception):
pass
class FullMatchError(Exception):
pass
class ArcSightBackend(SingleTextQueryBackend):
"""
Converts Sigma rule into ArcSight saved search.
Contributed by SOC Prime. https://socprime.com
"""
identifier = "as"
active = True
andToken = " AND "
orToken = " OR "
notToken = " NOT "
subExpression = "(%s)"
listExpression = "(%s)"
listSeparator = " OR "
valueExpression = "\"%s\""
containsExpression = "%s CONTAINS %s"
nullExpression = "NOT _exists_:%s"
notNullExpression = "_exists_:%s"
mapExpression = "%s = %s"
mapListsSpecialHandling = True
mapListValueExpression = "%s = %s"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
aFL = ["deviceVendor", "categoryDeviceGroup", "deviceProduct"]
for item in self.sigmaconfig.fieldmappings.values():
if item.target_type is list:
aFL.extend(item.target)
else:
aFL.append(item.target)
self.allowedFieldsList = list(set(aFL))
# Skip logsource value from sigma document for separate path.
def generateCleanValueNodeLogsource(self, value):
return self.valueExpression % (self.cleanValue(str(value)))
# Clearing values from special characters.
def CleanNode(self, node):
search_ptrn = re.compile(r"[\/\\@?#&_%*',\(\)\" ]")
replace_ptrn = re.compile(r"[ \/\\@?#&_%*',\(\)\" ]")
match = search_ptrn.search(node)
new_node = list()
if match:
replaced_str = replace_ptrn.sub('*', node)
node = [x for x in replaced_str.split('*') if x]
new_node.extend(node)
else:
new_node.append(node)
node = new_node
return node
# Clearing values from special characters.
def generateMapItemNode(self, node):
key, value = node
if key in self.allowedFieldsList:
if self.mapListsSpecialHandling == False and type(value) in (
str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int):
return self.mapExpression % (key, self.generateCleanValueNodeLogsource(value))
elif type(value) is list:
return self.generateMapItemListNode(key, value)
else:
raise TypeError("Backend does not support map values of type " + str(type(value)))
else:
if self.mapListsSpecialHandling == False and type(value) in (
str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int):
if type(value) is str:
new_value = list()
value = self.CleanNode(value)
if type(value) == list:
new_value.append(self.andToken.join([self.valueExpression % val for val in value]))
else:
new_value.append(value)
if len(new_value)==1:
return "(" + self.generateANDNode(new_value) + ")"
else:
return "(" + self.generateORNode(new_value) + ")"
else:
return self.generateValueNode(value)
elif type(value) is list:
new_value = list()
for item in value:
item = self.CleanNode(item)
if type(item) is list and len(item) == 1:
new_value.append(self.valueExpression % item[0])
elif type(item) is list:
new_value.append(self.andToken.join([self.valueExpression % val for val in item]))
else:
new_value.append(item)
return self.generateORNode(new_value)
else:
raise TypeError("Backend does not support map values of type " + str(type(value)))
# for keywords values with space
def generateValueNode(self, node):
if type(node) is int:
return self.cleanValue(str(node))
if 'AND' in node:
return "(" + self.cleanValue(str(node)) + ")"
else:
return self.cleanValue(str(node))
# collect elements of Arcsight search using OR
def generateMapItemListNode(self, key, value):
itemslist = list()
for item in value:
if key in self.allowedFieldsList:
itemslist.append('%s = %s' % (key, self.generateValueNode(item)))
else:
itemslist.append('%s' % (self.generateValueNode(item)))
return " OR ".join(itemslist)
# prepare of tail for every translate
def generate(self, sigmaparser):
"""Method is called for each sigma rule and receives the parsed rule (SigmaParser)"""
const_title = ' AND type != 2 | rex field = flexString1 mode=sed "s//Sigma: {}/g"'
for parsed in sigmaparser.condparsed:
self.output.print(self.generateQuery(parsed) + const_title.format(sigmaparser.parsedyaml["title"]))
# Add "( )" for values
def generateSubexpressionNode(self, node):
return self.subExpression % self.generateNode(node.items)
# generateORNode algorithm for ArcSightBackend class.
def generateORNode(self, node):
if type(node) == sigma.parser.ConditionOR and all(isinstance(item, str) for item in node):
new_value = list()
for value in node:
value = self.CleanNode(value)
if type(value) is list:
new_value.append(self.andToken.join([self.valueExpression % val for val in value]))
else:
new_value.append(value)
return "(" + self.orToken.join([self.generateNode(val) for val in new_value]) + ")"
return "(" + self.orToken.join([self.generateNode(val) for val in node]) + ")"
class QualysBackend(SingleTextQueryBackend):
"""
Converts Sigma rule into Qualys saved search.
Contributed by SOC Prime. https://socprime.com
"""
identifier = "qualys"
active = True
andToken = " and "
orToken = " or "
notToken = "not "
subExpression = "(%s)"
listExpression = "%s"
listSeparator = " "
valueExpression = "%s"
nullExpression = "%s is null"
notNullExpression = "not (%s is null)"
mapExpression = "%s:`%s`"
mapListsSpecialHandling = True
PartialMatchFlag = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
fl = []
for item in self.sigmaconfig.fieldmappings.values():
if item.target_type == list:
fl.extend(item.target)
else:
fl.append(item.target)
self.allowedFieldsList = list(set(fl))
def generateORNode(self, node):
new_list = []
for val in node:
if type(val) == tuple and not(val[0] in self.allowedFieldsList):
pass
# self.PartialMatchFlag = True
else:
new_list.append(val)
return self.orToken.join([self.generateNode(val) for val in new_list])
def generateANDNode(self, node):
new_list = []
for val in node:
if type(val) == tuple and not(val[0] in self.allowedFieldsList):
self.PartialMatchFlag = True
else:
new_list.append(val)
return self.andToken.join([self.generateNode(val) for val in new_list])
def generateMapItemNode(self, node):
key, value = node
if self.mapListsSpecialHandling == False and type(value) in (str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int):
if key in self.allowedFieldsList:
return self.mapExpression % (key, self.generateNode(value))
else:
return self.generateNode(value)
elif type(value) == list:
return self.generateMapItemListNode(key, value)
else:
raise TypeError("Backend does not support map values of type " + str(type(value)))
def generateMapItemListNode(self, key, value):
itemslist = []
for item in value:
if key in self.allowedFieldsList:
itemslist.append('%s:`%s`' % (key, self.generateValueNode(item)))
else:
itemslist.append('%s' % (self.generateValueNode(item)))
return "(" + (" or ".join(itemslist)) + ")"
def generate(self, sigmaparser):
"""Method is called for each sigma rule and receives the parsed rule (SigmaParser)"""
all_keys = set()
for parsed in sigmaparser.condparsed:
if self.generateQuery(parsed) == "()":
self.PartialMatchFlag = None
sigmaparser_parsedyaml = sigmaparser.parsedyaml
if self.PartialMatchFlag == True:
raise PartialMatchError(self.generateQuery(parsed))
elif self.PartialMatchFlag == None:
raise FullMatchError(self.generateQuery(parsed))
else:
print(self.generateQuery(parsed))
+10
View File
@@ -165,6 +165,16 @@ for sigmafile in get_inputs(cmdargs.inputs, cmdargs.recurse):
error = 42
if not cmdargs.defer_abort:
sys.exit(error)
except backends.PartialMatchError as e:
print("%s" % (str(e),), file=sys.stderr)
error = 80
if not cmdargs.defer_abort:
sys.exit(error)
except backends.FullMatchError as e:
print("Full Mismatch Error", file=sys.stderr)
error = 90
if not cmdargs.defer_abort:
sys.exit(error)
finally:
try:
f.close()