Files
blue-team-tools/tools/sigma/filter.py
T

250 lines
9.9 KiB
Python
Raw Normal View History

2017-02-13 23:29:56 +01:00
# Sigma parser
2018-07-27 22:35:30 +02:00
# Copyright 2016-2018 Thomas Patzke, Florian Roth
2017-12-07 21:55:43 +01:00
# 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/>.
2017-02-13 23:29:56 +01:00
2017-11-02 00:02:15 +01:00
# Rule Filtering
import datetime
2017-11-02 00:02:15 +01:00
class SigmaRuleFilter:
"""Filter for Sigma rules with conditions"""
LEVELS = {
"low" : 0,
"medium" : 1,
"high" : 2,
"critical" : 3
}
2021-10-28 20:08:27 +02:00
STATES = [
"unsupported",
"deprecated",
"experimental",
2021-10-14 07:19:41 +02:00
"test",
"stable"]
2017-11-02 00:02:15 +01:00
def __init__(self, expr):
2021-06-10 08:26:19 +02:00
self.minlevel = None
self.maxlevel = None
2021-05-22 09:04:30 +02:00
self.status = None
2021-10-28 20:56:19 +02:00
self.notstatus = None
2021-08-11 14:26:20 +02:00
self.tlp = None
self.target = None
2021-05-22 09:04:30 +02:00
self.logsources = list()
self.notlogsources = list()
self.tags = list()
self.nottags = list()
self.inlastday = None
2021-06-10 08:26:19 +02:00
self.condition = list()
self.notcondition = list()
2017-11-02 00:02:15 +01:00
for cond in [c.replace(" ", "") for c in expr.split(",")]:
if cond.startswith("level<="):
try:
level = cond[cond.index("=") + 1:]
self.maxlevel = self.LEVELS[level]
except KeyError as e:
raise SigmaRuleFilterParseException("Unknown level '%s' in condition '%s'" % (level, cond)) from e
elif cond.startswith("level>="):
try:
level = cond[cond.index("=") + 1:]
self.minlevel = self.LEVELS[level]
except KeyError as e:
raise SigmaRuleFilterParseException("Unknown level '%s' in condition '%s'" % (level, cond)) from e
elif cond.startswith("level="):
try:
level = cond[cond.index("=") + 1:]
self.minlevel = self.LEVELS[level]
self.maxlevel = self.minlevel
except KeyError as e:
raise SigmaRuleFilterParseException("Unknown level '%s' in condition '%s'" % (level, cond)) from e
elif cond.startswith("status="):
self.status = cond[cond.index("=") + 1:]
if self.status not in self.STATES:
raise SigmaRuleFilterParseException("Unknown status '%s' in condition '%s'" % (self.status, cond))
2021-10-28 19:46:36 +02:00
elif cond.startswith("status!="):
self.notstatus = cond[cond.index("=") + 1:]
if self.notstatus not in self.STATES:
raise SigmaRuleFilterParseException("Unknown status '%s' in condition '%s'" % (self.notstatus, cond))
2021-08-11 14:26:20 +02:00
elif cond.startswith("tlp="):
2021-08-18 19:00:57 +00:00
self.tlp = cond[cond.index("=") + 1:].upper() #tlp is always uppercase
2021-08-11 14:26:20 +02:00
elif cond.startswith("target="):
self.target = cond[cond.index("=") + 1:].lower() # lower to make caseinsensitive
2017-11-02 00:02:15 +01:00
elif cond.startswith("logsource="):
self.logsources.append(cond[cond.index("=") + 1:])
2021-05-22 09:04:30 +02:00
elif cond.startswith("logsource!="):
self.notlogsources.append(cond[cond.index("=") + 1:])
2018-09-06 00:57:54 +02:00
elif cond.startswith("tag="):
self.tags.append(cond[cond.index("=") + 1:].lower())
2021-05-22 08:57:42 +02:00
elif cond.startswith("tag!="):
2021-06-10 08:26:19 +02:00
self.nottags.append(cond[cond.index("=") + 1:].lower())
elif cond.startswith("condition="):
self.condition.append(cond[cond.index("=") + 1:].lower())
elif cond.startswith("condition!="):
self.notcondition.append(cond[cond.index("=") + 1:].lower())
elif cond.startswith("inlastday="):
nbday = cond[cond.index("=") + 1:]
2021-06-10 08:26:19 +02:00
try:
self.inlastday = int(nbday)
except ValueError as e:
raise SigmaRuleFilterParseException("Unknown number '%s' in condition '%s'" % (nbday, cond)) from e
2017-11-02 00:02:15 +01:00
else:
raise SigmaRuleFilterParseException("Unknown condition '%s'" % cond)
def match(self, yamldoc):
"""Match filter conditions against rule"""
# Levels
if self.minlevel is not None or self.maxlevel is not None:
try:
level = self.LEVELS[yamldoc['level']]
except KeyError: # missing or invalid level
return False # User wants level restriction, but it's not possible here
# Minimum level
if self.minlevel is not None:
if level < self.minlevel:
return False
# Maximum level
if self.maxlevel is not None:
if level > self.maxlevel:
return False
# Status
if self.status is not None:
try:
status = yamldoc['status']
except KeyError: # missing status
return False # User wants status restriction, but it's not possible here
if status != self.status:
return False
2021-10-28 20:56:19 +02:00
2021-10-28 19:46:36 +02:00
if self.notstatus is not None:
try:
status = yamldoc['status']
except KeyError: # missing status
return False # User wants status restriction, but it's not possible here
if status == self.notstatus:
return False
2017-11-02 00:02:15 +01:00
2021-08-11 14:26:20 +02:00
# Tlp
if self.tlp is not None:
try:
tlp = yamldoc['tlp']
except KeyError: # missing tlp
tlp = "WHITE" # tlp is WHITE by default
if tlp != self.tlp:
return False
#Target
if self.target:
try:
targets = [ target.lower() for target in yamldoc['target']]
except (KeyError, AttributeError): # no target set
return False
if self.target not in targets:
return False
2017-11-02 00:02:15 +01:00
# Log Sources
2018-09-06 00:57:54 +02:00
if self.logsources:
2017-11-02 00:02:15 +01:00
try:
logsources = { value for key, value in yamldoc['logsource'].items() }
except (KeyError, AttributeError): # no log source set
return False # User wants status restriction, but it's not possible here
2021-06-10 08:49:15 +02:00
2017-11-02 00:02:15 +01:00
for logsrc in self.logsources:
if logsrc not in logsources:
return False
2021-05-22 09:04:30 +02:00
# NOT Log Sources
if self.notlogsources:
try:
notlogsources = { value for key, value in yamldoc['logsource'].items() }
except (KeyError, AttributeError): # no log source set
return False # User wants status restriction, but it's not possible here
for logsrc in self.notlogsources:
if logsrc in notlogsources:
return False
2018-09-06 00:57:54 +02:00
# Tags
if self.tags:
try:
tags = [ tag.lower() for tag in yamldoc['tags']]
except (KeyError, AttributeError): # no tags set
return False
for tag in self.tags:
if tag not in tags:
return False
2021-05-22 09:04:30 +02:00
# NOT Tags
2021-05-22 08:57:42 +02:00
if self.nottags:
try:
nottags = [ tag.lower() for tag in yamldoc['tags']]
except (KeyError, AttributeError): # no tags set
return False
for tag in self.nottags:
if tag in nottags:
return False
2021-06-10 08:26:19 +02:00
# date in the last N days
if self.inlastday:
try:
date_str = yamldoc['date']
2021-06-10 08:26:19 +02:00
except KeyError: # missing date
return False # User wants date time restriction, but it's not possible here
2021-06-10 08:26:19 +02:00
try:
modified_str = yamldoc['modified']
2021-06-10 08:26:19 +02:00
except KeyError: # no update
modified_str = None
if modified_str:
date_str = modified_str
2021-06-10 08:26:19 +02:00
date_object = datetime.datetime.strptime(date_str, '%Y/%m/%d')
today_objet = datetime.datetime.now()
delta = today_objet - date_object
if delta.days > self.inlastday:
return False
2021-06-10 08:26:19 +02:00
if self.condition:
try:
conditions = yamldoc['detection']['condition']
if isinstance(conditions,list): # sone time conditions are list even with only 1 line
s_condition = ' '.join(conditions)
else:
s_condition = conditions
except KeyError: # missing condition
return False # User wants condition restriction, but it's not possible here
for val in self.condition:
if not val in s_condition:
return False
if self.notcondition:
try:
conditions = yamldoc['detection']['condition']
if isinstance(conditions,list): # sone time conditions are list even with only 1 line
s_condition = ' '.join(conditions)
else:
s_condition = conditions
except KeyError: # missing condition
return False # User wants condition restriction, but it's not possible here
for val in self.notcondition:
if val in s_condition:
return False
2017-11-02 00:02:15 +01:00
# all tests passed
return True
class SigmaRuleFilterParseException(Exception):
pass