Updated Azure Sentinel backend

This commit is contained in:
vh
2020-01-31 17:17:24 +02:00
2 changed files with 100 additions and 91 deletions
-81
View File
@@ -1,81 +0,0 @@
# Azure Log Analytics output backend for sigmac
# John Tuckner (@tuckner)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import xml.etree.ElementTree as xml
from sigma.backends.ala import AzureLogAnalyticsBackend
from .base import SingleTextQueryBackend
from .data import sysmon_schema
from .exceptions import NotSupportedError
class AzureAPIBackend(AzureLogAnalyticsBackend):
"""Converts Sigma rule into Azure Log Analytics Rules."""
identifier = "ala-rule"
active = True
options = SingleTextQueryBackend.options + (
("sysmon", False, "Generate Sysmon event queries for generic rules", None),
)
def __init__(self, *args, **kwargs):
"""Initialize field mappings"""
super().__init__(*args, **kwargs)
def create_rule(self, config):
tags = config.get("tags")
tactics = list()
technics = list()
for tag in tags:
tag = tag.replace("attack.", "")
if re.match("[tT][0-9]{4}", tag):
technics.append(tag.title())
else:
if "_" in tag:
tag_list = tag.split("_")
tag_list = [item.title() for item in tag_list]
tactics.append("".join(tag_list))
else:
tactics.append(tag.title())
rule = {
"analytics":
[
{
"displayName": "{} by {}".format(config.get("title"), config.get('author')),
"description": "{} {}".format(config.get("description"), "Technique: {}.".format(",".join(technics))),
"severity": config.get("level"),
"enabled": True,
"query": config.get("translation"),
"queryFrequency": "12H",
"queryPeriod": "12H",
"triggerOperator": "GreaterThan",
"triggerThreshold": 1,
"suppressionDuration": "12H",
"suppressionEnabled": False,
"tactics": tactics
}
]
}
return rule
def generate(self, sigmaparser):
translation = super().generate(sigmaparser)
configs = sigmaparser.parsedyaml
configs.update({"translation": translation})
rule = self.create_rule(configs)
return rule
+100 -10
View File
@@ -16,6 +16,8 @@
import re
import xml.etree.ElementTree as xml
from ..parser.modifiers.type import SigmaRegularExpressionModifier
from .base import SingleTextQueryBackend
from .data import sysmon_schema
from .exceptions import NotSupportedError
@@ -44,9 +46,29 @@ class AzureLogAnalyticsBackend(SingleTextQueryBackend):
mapListsSpecialHandling = True
mapListValueExpression = "%s in %s"
_WIN_SECURITY_EVENT_MAP = {
"Image": "NewProcessName",
"ParentImage": "ParentProcessName",
"User": "SubjectUserName",
}
def __init__(self, *args, **kwargs):
"""Initialize field mappings"""
super().__init__(*args, **kwargs)
self.category = None
self.product = None
self.service = None
self.table = None
self.eventid = None
self._parser = None
self._fields = None
self._agg_var = None
self._has_logsource_event_cond = False
if not self.sysmon and not self.sigmaconfig.config:
self._field_map = self._WIN_SECURITY_EVENT_MAP
else:
self._field_map = {}
self.typedValueExpression[SigmaRegularExpressionModifier] = "matches regex \"%s\""
def id_mapping(self, src):
"""Identity mapping, source == target field name"""
@@ -76,6 +98,8 @@ class AzureLogAnalyticsBackend(SingleTextQueryBackend):
val = re.sub('([".^$]|\\\\(?![*?]))', '\\\\\g<1>', val)
val = re.sub('\\*', '.*', val)
val = re.sub('\\?', '.', val)
if "\\" in val:
return "%s @\"%s\"" % (op, val)
elif type(val) == str: # value possibly only starts and/or ends with *, use prefix/postfix match
if val.endswith("*") and val.startswith("*"):
op = "contains"
@@ -88,7 +112,6 @@ class AzureLogAnalyticsBackend(SingleTextQueryBackend):
val = self.cleanValue(val[1:])
if "\\" in val:
#val = val.replace("\\", "\\\\")
return "%s @\"%s\"" % (op, val)
return "%s \"%s\"" % (op, val)
@@ -104,13 +127,26 @@ class AzureLogAnalyticsBackend(SingleTextQueryBackend):
self.product = None
self.service = None
detection = sigmaparser.parsedyaml.get("detection", {})
is_parent_cmd = False
if "keywords" in detection.keys():
return super().generate(sigmaparser)
if self.category == "process_creation":
if self.sysmon:
self.table = "Event"
self.eventid = "1"
else:
self.table = "SecurityEvent"
self.eventid = "4688"
self.table = "SysmonEvent"
self.eventid = "1"
elif self.service == "security":
self.table = "SecurityEvent"
elif self.service == "sysmon":
self.table = "SysmonEvent"
elif self.service == "powershell":
self.table = "Event"
else:
if self.service:
self.table = self.service.title()
elif self.product:
self.table = self.product.title()
return super().generate(sigmaparser)
@@ -121,13 +157,13 @@ class AzureLogAnalyticsBackend(SingleTextQueryBackend):
parse_string = self.map_sysmon_schema(self.eventid)
before = "%s | parse EventData with * %s | where EventID == \"%s\" | where " % (self.table, parse_string, self.eventid)
elif self.sysmon:
parse_string = self.map_sysmon_schema(self.eventid)
parse_string = self.map_sysmon_schema(self.eventid)
before = "%s | parse EventData with * %s | where " % (self.table, parse_string)
elif self.category == "process_creation":
before = "%s | where EventID == \"%s\" | where " % (self.table, self.eventid)
else:
before = "%s | where " % self.table
return before
return before
def generateMapItemNode(self, node):
"""
@@ -141,8 +177,10 @@ class AzureLogAnalyticsBackend(SingleTextQueryBackend):
) + ")"
elif key == "EventID": # EventIDs are not reflected in condition but in table selection
if self.service == "sysmon":
self.table = "Event"
self.table = "SysmonEvent"
self.eventid = value
elif self.service == "powershell":
self.table = "Event"
elif self.service == "security":
self.table = "SecurityEvent"
elif self.service == "system":
@@ -172,3 +210,55 @@ class AzureLogAnalyticsBackend(SingleTextQueryBackend):
raise TypeError("Backend does not support map values of type " + str(type(value)))
return super().generateMapItemNode(node)
class AzureAPIBackend(AzureLogAnalyticsBackend):
"""Converts Sigma rule into Azure Log Analytics Rule."""
identifier = "ala-rule"
active = True
options = SingleTextQueryBackend.options + (
("sysmon", False, "Generate Sysmon event queries for generic rules", None),
)
def __init__(self, *args, **kwargs):
"""Initialize field mappings"""
super().__init__(*args, **kwargs)
def create_rule(self, config):
tags = config.get("tags")
tactics = list()
technics = list()
for tag in tags:
tag = tag.replace("attack.", "")
if re.match("[tT][0-9]{4}", tag):
technics.append(tag.title())
else:
if "_" in tag:
tag_list = tag.split("_")
tag_list = [item.title() for item in tag_list]
tactics.append("".join(tag_list))
else:
tactics.append(tag.title())
rule = {
"displayName": "{} by {}".format(config.get("title"), config.get('author')),
"description": "{} {}".format(config.get("description"), "Technique: {}.".format(",".join(technics))),
"severity": config.get("level"),
"enabled": True,
"query": config.get("translation"),
"queryFrequency": "12H",
"queryPeriod": "12H",
"triggerOperator": "GreaterThan",
"triggerThreshold": 1,
"suppressionDuration": "12H",
"suppressionEnabled": False,
"tactics": tactics
}
return rule
def generate(self, sigmaparser):
translation = super().generate(sigmaparser)
configs = sigmaparser.parsedyaml
configs.update({"translation": translation})
rule = self.create_rule(configs)
return rule