Added ArcSight & Qualys backends
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user