Skip to content

Commit

Permalink
Merge pull request SigmaHQ#2090 from roysjosh/ala-near
Browse files Browse the repository at this point in the history
Implement "near" support for ALA/Sentinel
  • Loading branch information
thomaspatzke authored Oct 16, 2021
2 parents 00dd72a + 0f3b169 commit e6881e4
Showing 1 changed file with 71 additions and 5 deletions.
76 changes: 71 additions & 5 deletions tools/sigma/backends/ala.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from sigma.config.mapping import (
SimpleFieldMapping, MultiFieldMapping, ConditionalFieldMapping
)
from sigma.parser.condition import SigmaAggregationParser
from sigma.parser.condition import SigmaAggregationParser, SigmaConditionParser, SigmaConditionTokenizer

from sigma.parser.modifiers.type import SigmaRegularExpressionModifier
from sigma.backends.base import SingleTextQueryBackend
Expand Down Expand Up @@ -82,6 +82,8 @@ def __init__(self, *args, **kwargs):
self.service = None
self.table = None
self.eventid = None
self.tableAggJoinFields = None
self.tableAggTimeField = None
self._parser = None
self._fields = None
self._agg_var = None
Expand Down Expand Up @@ -142,6 +144,8 @@ def getTable(self, sigmaparser):
"CommandLine"}) == 0:
self.table = "SecurityEvent | where EventID == 4688 "
self.eventid = "4688"
self.tableAggJoinFields = "SubjectLogonId, Computer"
self.tableAggTimeField = "TimeGenerated"
elif self.category == "process_creation":
self.table = "SysmonEvent"
self.eventid = "1"
Expand Down Expand Up @@ -219,6 +223,12 @@ def generateBefore(self, parsed):
# before = "%s | where EventID == \"%s\" | where " % (self.table, self.eventid)
else:
before = "%s | where " % self.table
if parsed.parsedAgg != None and parsed.parsedAgg.aggfunc == SigmaAggregationParser.AGGFUNC_NEAR:
window = parsed.parsedAgg.parser.parsedyaml["detection"].get("timeframe", "30m")
before = """
let lookupWindow = %s;
let lookupBin = lookupWindow / 2.0;
""" % (window) + before
return before

def generateMapItemNode(self, node):
Expand Down Expand Up @@ -294,14 +304,70 @@ def generateTypedValueNode(self, node):
except KeyError:
raise NotImplementedError("Type modifier '{}' is not supported by backend".format(node.identifier))

def generateAggregationQuery(self, agg, searchId):
condtoken = SigmaConditionTokenizer(searchId)
condparsed = SigmaConditionParser(agg.parser, condtoken)
backend = AzureLogAnalyticsBackend(agg.config)

# these bits from generate() should be moved to __init__
try:
backend.category = agg.parser.parsedyaml['logsource'].setdefault('category', None)
backend.product = agg.parser.parsedyaml['logsource'].setdefault('product', None)
backend.service = agg.parser.parsedyaml['logsource'].setdefault('service', None)
except KeyError:
backend.category = None
backend.product = None
backend.service = None
backend.getTable(agg.parser)

query = backend.generateQuery(condparsed)
before = backend.generateBefore(condparsed)
return before + query

# follow the join/time window pattern
# https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/join-timewindow
def generateNear(self, agg):
includeQueries = []

includeCount = 0
for includeCount, include in enumerate(agg.include, start=1):
iq = self.generateAggregationQuery(agg, include)
iq += """
| extend End{timeIndex}={timeField},
TimeKey = range(
bin({timeField} - lookupWindow, lookupBin),
bin({timeField}, lookupBin),
lookupBin)
| mv-expand TimeKey to typeof(datetime)""".format(
timeField=self.tableAggTimeField,
timeIndex=includeCount,
)
includeQueries.append(iq)

ret = " | extend Start={timeField}, TimeKey = bin({timeField}, lookupBin) | join kind=inner (\n ".format(
timeField=self.tableAggTimeField,
)
ret += ") on {joinFields}, TimeKey | join kind=inner (\n ".format(
joinFields=self.tableAggJoinFields,
).join(includeQueries)
ret += ") on {joinFields}, TimeKey\n| where ".format(
joinFields=self.tableAggJoinFields,
)
ret += " and ".join([
"(End%d - Start) between (0min .. lookupWindow)" % (endIndex + 1) for endIndex in range(includeCount)
])

return ret

def generateAggregation(self, agg):
if agg is None:
return ""
if agg.aggfunc == SigmaAggregationParser.AGGFUNC_NEAR:
raise NotImplementedError(
"The 'near' aggregation operator is not "
+ f"implemented for the %s backend" % self.identifier
)
if agg.exclude:
raise NotSupportedError("This backend doesn't currently support 'near' with excludes")
if self.tableAggJoinFields == None or self.tableAggTimeField == None:
raise NotSupportedError("This backend doesn't currently support 'near' for this table")
return self.generateNear(agg)
if agg.aggfunc_notrans != 'count' and agg.aggfield is None:
raise NotSupportedError(
"The '%s' aggregation operator " % agg.aggfunc_notrans
Expand Down

0 comments on commit e6881e4

Please sign in to comment.