From 294bb432d0f2cb9b915247ddea47c8a7d51d2e0a Mon Sep 17 00:00:00 2001 From: Joshua Roys Date: Tue, 24 Aug 2021 16:01:23 -0400 Subject: [PATCH] Add Azure Sentinel backend The web interface expects ARM templates. --- tools/config/ala.yml | 1 + tools/sigma/backends/ala.py | 97 +++++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/tools/config/ala.yml b/tools/config/ala.yml index 1f4dd8ffc..e9110fc72 100644 --- a/tools/config/ala.yml +++ b/tools/config/ala.yml @@ -3,6 +3,7 @@ order: 20 backends: - ala - ala-rule + - sentinel-rule fieldmappings: ComputerName: Computer Event-ID: EventID diff --git a/tools/sigma/backends/ala.py b/tools/sigma/backends/ala.py index cc2810390..ef90618c3 100644 --- a/tools/sigma/backends/ala.py +++ b/tools/sigma/backends/ala.py @@ -19,6 +19,8 @@ import re import json import xml.etree.ElementTree as xml +from datetime import timedelta +from uuid import uuid4 from sigma.config.mapping import ( SimpleFieldMapping, MultiFieldMapping, ConditionalFieldMapping @@ -423,6 +425,28 @@ class AzureAPIBackend(AzureLogAnalyticsBackend): return tactics, technics + def timeframeToDelta(self, timeframe): + time_unit = timeframe[-1:] + duration = int(timeframe[:-1]) + return ( + time_unit == "s" and timedelta(seconds=duration) or + time_unit == "m" and timedelta(minutes=duration) or + time_unit == "h" and timedelta(hours=duration) or + time_unit == "d" and timedelta(days=duration) or + None + ) + + def iso8601_duration(self, delta): + if not delta: + return "PT0S" + if not delta.seconds: + return "P%dD" % (delta.days) + days = delta.days and "%dD" % (delta.days) or "" + hours = delta.seconds // 3600 % 24 and "%dH" % (delta.seconds // 3600 % 24) or "" + minutes = delta.seconds // 60 % 60 and "%dM" % (delta.seconds // 60 % 60) or "" + seconds = delta.seconds % 60 and "%dS" % (delta.seconds % 60) or "" + return "P%sT%s%s%s" % (days, hours, minutes, seconds) + def create_rule(self, config): tags = config.get("tags", []) @@ -430,17 +454,21 @@ class AzureAPIBackend(AzureLogAnalyticsBackend): tactics, technics = self.skip_tactics_or_techniques(technics, tactics) tactics = list(map(lambda s: s.replace(" ", ""), tactics)) + timeframe = self.timeframeToDelta(config["detection"].setdefault("timeframe", "30m")) + queryDuration = self.iso8601_duration(timeframe) + suppressionDuration = self.iso8601_duration(timeframe * 5) + rule = { "displayName": "{} by {}".format(config.get("title"), config.get('author')), "description": "{} {}".format(config.get("description"), "Technique: {}.".format(",".join(technics))), "severity": self.parse_severity(config.get("level", "medium")), "enabled": True, "query": config.get("translation"), - "queryFrequency": "12H", - "queryPeriod": "12H", + "queryFrequency": queryDuration, + "queryPeriod": queryDuration, "triggerOperator": "GreaterThan", "triggerThreshold": 0, - "suppressionDuration": "12H", + "suppressionDuration": suppressionDuration, "suppressionEnabled": True, "tactics": tactics } @@ -455,3 +483,66 @@ class AzureAPIBackend(AzureLogAnalyticsBackend): return rule else: raise NotSupportedError("No table could be determined from Sigma rule") + +class SentinelBackend(AzureAPIBackend): + """Converts Sigma rule into Azure Sentinel scheduled alert rule ARM template.""" + identifier = "sentinel-rule" + active = True + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def generate(self, sigmaparser): + translation = super().generate(sigmaparser) + if translation: + configs = sigmaparser.parsedyaml + configs.update({"translation": translation}) + rule = self.create_sentinel_rule(configs) + return json.dumps(rule) + + def create_sentinel_rule(self, config): + # https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/child-resource-name-type#outside-parent-resource + # https://docs.microsoft.com/en-us/azure/templates/microsoft.operationalinsights/workspaces?tabs=json + # https://docs.microsoft.com/en-us/rest/api/securityinsights/alert-rules/create-or-update#scheduledalertrule + properties = json.loads(config.get("translation")) + properties.update({ + "incidentConfiguration": { + "createIncident": True, + "groupingConfiguration": { + "enabled": False, + "reopenClosedIncident": False, + "lookbackDuration": properties['suppressionDuration'], + "matchingMethod": "AllEntities", + "groupByEntities": [], + "groupByAlertDetails": [], + "groupByCustomDetails": [], + }, + }, + "eventGroupingSettings": { + "aggregationKind": "SingleAlert", + }, + "alertDetailsOverride": None, + "customDetails": None, + "templateVersion": "1.0.0", + }) + rule_uuid = config.get("id", str(uuid4())) + return { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "workspace": { + "type": "String", + }, + }, + "resources": [ + { + "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/" + rule_uuid + "')]", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/" + rule_uuid + "')]", + "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules", + "apiVersion": "2021-03-01-preview", + + "kind": "Scheduled", + "properties": properties, + }, + ], + }