This commit is contained in:
Mike Wade 2020-09-15 06:24:44 -06:00
commit da9b32bdd6
12 changed files with 511 additions and 82 deletions

View File

@ -57,6 +57,7 @@ test-sigmac:
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t carbonblack -c tools/config/carbon-black.yml rules/ > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t qualys -c tools/config/qualys.yml rules/ > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t netwitness -c tools/config/netwitness.yml rules/ > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t netwitness-epl -c netwitness-epl rules/ > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t sumologic -O rulecomment -c tools/config/sumologic.yml rules/ > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t humio -O rulecomment -c tools/config/humio.yml rules/ > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t crowdstrike -O rulecomment -c tools/config/crowdstrike.yml rules/ > /dev/null
@ -64,6 +65,7 @@ test-sigmac:
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t sqlite -c sysmon rules/ > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t csharp -c sysmon rules/ > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t logiq -c sysmon rules/ > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t sysmon -c sysmon -rvd rules/windows/driver_load rules/windows/file_event rules/windows/image_load rules/windows/network_connection rules/windows/process_access rules/windows/process_creation rules/windows/registry_event rules/windows/sysmon > /dev/null
$(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunk -c tools/config/splunk-windows-index.yml -f 'level>=high,level<=critical,status=stable,logsource=windows,tag=attack.execution' rules/ > /dev/null
! $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunk -c tools/config/splunk-windows-index.yml -f 'level>=high,level<=critical,status=xstable,logsource=windows' rules/ > /dev/null
! $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunk -c tools/config/splunk-windows-index.yml -f 'level>=high,level<=xcritical,status=stable,logsource=windows' rules/ > /dev/null

View File

@ -3,8 +3,8 @@ id: 611eab06-a145-4dfa-a295-3ccc5c20f59a
description: Detects Mimikatz DC sync security events
status: experimental
date: 2018/06/03
modified: 2019/10/08
author: Benjamin Delpy, Florian Roth
modified: 2020/09/11
author: Benjamin Delpy, Florian Roth, Scott Dermott
references:
- https://twitter.com/gentilkiwi/status/1003236624925413376
- https://gist.github.com/gentilkiwi/dcc132457408cf11ad2061340dcb53c2
@ -28,6 +28,7 @@ detection:
SubjectUserName:
- 'NT AUTHORITY*'
- '*$'
- 'MSOL_*'
condition: selection and not filter1 and not filter2
falsepositives:
- Valid DC Sync that is not covered by the filters; please report

View File

@ -6,7 +6,7 @@ references:
tags:
- attack.defense_evasion
- attack.t1054 # an old one
- attack.t1562.006
- attack.t1562.002
author: '@neu5ron'
date: 2017/11/19
logsource:

View File

@ -10,9 +10,8 @@ date: 2020/07/14
tags:
- attack.execution
- attack.lateral_movement
- attack.t1570
- attack.t1047
- attack.t1569
- attack.t1035 # an old one
- attack.t1569.002
logsource:
product: windows_defender

View File

@ -10,7 +10,7 @@ references:
- https://twitter.com/timbmsft/status/900724491076214784
tags:
- attack.defense_evasion
- attck.t1562.002
- attack.t1562.002
- attack.t1089 # an old one
logsource:
category: process_access

View File

@ -11,7 +11,7 @@ date: 2019/03/22
tags:
- attack.defense_evasion
- attack.t1070
- attack.t1562
- attack.t1562.006
- car.2016-04-002
level: high
logsource:

View File

@ -8,7 +8,9 @@ author: Florian Roth
date: 2020/06/04
tags:
- attack.execution
- attack.defense_evasion
- attack.t1059.001
- attack.t1564.003
- attack.t1086 # an old one
logsource:
category: process_creation

View File

@ -1,74 +0,0 @@
title: Suspicious Process Creation
id: 5f0f47a5-cb16-4dbe-9e31-e8d976d73de3
status: experimental
description: Detects suspicious process starts on Windows systems based on keywords
author: Florian Roth, Daniil Yugoslavskiy, oscd.community (update)
date: 2018/01/01
modified: 2019/11/01
references:
- https://www.swordshield.com/2015/07/getting-hashes-from-ntds-dit-file/
- https://www.youtube.com/watch?v=H3t_kHQG1Js&feature=youtu.be&t=15m35s
- https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/
- https://twitter.com/subTee/status/872244674609676288
- https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/remote-tool-examples
- https://tyranidslair.blogspot.ca/2017/07/dg-on-windows-10-s-executing-arbitrary.html
- https://www.trustedsec.com/2017/07/new-tool-release-nps_payload/
- https://subt0x10.blogspot.ca/2017/04/bypassing-application-whitelisting.html
- https://gist.github.com/subTee/7937a8ef07409715f15b84781e180c46#file-rat-bat
- https://twitter.com/vector_sec/status/896049052642533376
- http://security-research.dyndns.org/pub/slides/FIRST-TC-2018/FIRST-TC-2018_Tom-Ueltschi_Sysmon_PUBLIC.pdf
logsource:
category: process_creation
product: windows
detection:
selection:
CommandLine:
- '* sekurlsa:*'
- net localgroup administrators * /add
- net group "Domain Admins" * /ADD /DOMAIN
- certutil.exe *-urlcache* http*
- certutil.exe *-urlcache* ftp*
- netsh advfirewall firewall *\AppData\\*
- attrib +S +H +R *\AppData\\*
- schtasks* /create *\AppData\\*
- schtasks* /sc minute*
- '*\Regasm.exe *\AppData\\*'
- '*\Regasm *\AppData\\*'
- '*\bitsadmin* /transfer*'
- '*\certutil.exe * -decode *'
- '*\certutil.exe * -decodehex *'
- '*\certutil.exe -ping *'
- icacls * /grant Everyone:F /T /C /Q
- '* wbadmin.exe delete catalog -quiet*'
- '*\wscript.exe *.jse'
- '*\wscript.exe *.js'
- '*\wscript.exe *.vba'
- '*\wscript.exe *.vbe'
- '*\cscript.exe *.jse'
- '*\cscript.exe *.js'
- '*\cscript.exe *.vba'
- '*\cscript.exe *.vbe'
- '*\fodhelper.exe'
- '*waitfor*/s*'
- '*waitfor*/si persist*'
- '*remote*/s*'
- '*remote*/c*'
- '*remote*/q*'
- '*AddInProcess*'
- '* /stext *'
- '* /scomma *'
- '* /stab *'
- '* /stabular *'
- '* /shtml *'
- '* /sverhtml *'
- '* /sxml *'
condition: selection
fields:
- ComputerName
- User
- CommandLine
falsepositives:
- False positives depend on scripts and administrative tools used in the monitored environment
level: medium
tags:
- car.2013-07-001

View File

@ -19,9 +19,9 @@ detection:
condition: selection
tags:
- attack.execution
- attack.persistence
- attack.t1177 # an old one
- attack.t1547.008
falsepositives:
- Unknown
level: high

View File

@ -0,0 +1,92 @@
title: NetWitness
order: 20
backends:
- netwitness-epl
logsources:
linux:
product: linux
conditions:
device.class: rhlinux
linux-sshd:
product: linux
service: sshd
conditions:
device.class: rhlinux
client: sshd
linux-auth:
product: linux
service: auth
conditions:
device.class: rhlinux
linux-clamav:
product: linux
service: clamav
conditions:
device.class: rhlinux
windows-sys:
product: windows
service: sysmon
conditions:
device.type: winevent_nic
event.source: microsoft-windows-security-auditing
windows-power:
product: windows
service: powershell
conditions:
device.type: winevent_nic
windows-dhcp:
product: windows
service: dhcp
conditions:
device.type: winevent_nic
event.source: microsoft-windows-dhcp-server
windows-sec:
product: windows
service: security
conditions:
device.type: winevent_nic
event.source: microsoft-windows-security-auditing
windows-system:
product: windows
service: system
conditions:
device.type: winevent_nic
fieldmappings:
dst:
- ip.dst
dst_ip:
- ip.dst
src:
- ip.src
src_ip:
- ip.src
DestinationPort:
- ip.dstport
EventID:
- reference.id
NewProcessName:
- process
LogonType:
- logon.type
AccountName:
- user.dst
c-uri-extension:
- extension
c-useragent:
- user.agent
r-dns:
- alias.host
DestinationHostname:
- alias.host
cs-host:
- alias.host
c-uri-query:
- web.page
c-uri:
- web.page
cs-method:
- action
cs-cookie:
- web.cookie
SubjectUserName:
- user.dst

View File

@ -0,0 +1,163 @@
# NetWitness EPL output backend for sigmac
# Copyright 2019 Tarik BOUDJEMAA (@snake-jump)
# Inspired from John Tuckner (@tuckner) NetWitness output backend for sigmac
# NetWitness EPL backend for sigmac uses netwitness-epl.yml config file
# RSA Alerts are generated by Event Processing Language (EPL) , that uses Esper Engine (https://www.espertech.com/esper/)
# For more details see :https://community.rsa.com/docs/DOC-110246 and https://community.rsa.com/docs/DOC-80068
# 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 sigma
from .base import SingleTextQueryBackend
from .mixins import MultiRuleOutputMixin
from sigma.parser.modifiers.base import SigmaTypeModifier
from sigma.parser.modifiers.type import SigmaRegularExpressionModifier
template="""
module_XXXXX;
@Name('RuleName')
@RSAAlert(oneInSeconds=0)
SELECT * FROM Event(
EXPRESSION
);"""
class NetWitnessEplBackend(SingleTextQueryBackend):
"""Converts Sigma rule into RSA NetWitness EPL . Contributed by @snake-jump"""
identifier = "netwitness-epl"
config_required = False
default_config = ["sysmon","netwitness-epl"]
active = True
reEscape = re.compile('(")')
#reEscape = re.compile("([\\|()\[\]{}.^$+])")
reClear = None
andToken = " AND "
orToken = " OR "
notToken = "NOT"
subExpression = "(%s)"
listExpression = "(%s)"
listSeparator = ", "
valueExpression = "\'%s\'"
keyExpression = "%s"
nullExpression = "%s exists"
notNullExpression = "%s exists"
mapExpression = "(%s=%s)"
mapListsSpecialHandling = True
def generateMapItemNode(self, node):
key, value = node
if type(key) != int:
key = key.replace(".","_") ## replace . by _ in meta name (RSA EPL)
key = key.lower()
if self.mapListsSpecialHandling == False and type(value) in (str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int):
if type(value) == str and "*" in value[1:-1]:
value = re.sub('([".^$]|\\\\(?![*?]))', '\\\\\g<1>', value)
value = re.sub('\\*', '.*', value)
value = re.sub('\\?', '.', value)
return "(%s REGEXP %s)" %(key, self.generateValueNode(value))
elif type(value) == str and "*" in value:
value = re.sub("(\*\\\\)|(\*)", "", value)
value = self.generateValueNode("%"+value+"%") # add "%" to construct the like expression ex: process like %psexesvc%
return "(%s LIKE %s)" % (key,value)
elif type(value) in (str, int):
return self.mapExpression % (key, self.generateValueNode(value))
else:
return self.mapExpression % (key, self.generateNode(value))
elif type(value) == list:
return self.generateMapItemListNode(key, value)
elif value is None:
return self.nullExpression % (key, )
elif type(value) == SigmaRegularExpressionModifier: ## if value is regex
regex = str(value)
## in RSA netwitness EPL regex each backslash must be escaped by backslash
## ex : c:\temp\ to regex -> c:\\temp\\ to RSA EPL regex --> c:\\\\temp\\\\
regex = regex.replace("\\","\\\\")
# Regular Expressions have to match the full value in RSA Netwitness EPL
if not (regex.startswith('^') or regex.startswith('.*')):
regex = '.*' + regex
if not (regex.endswith('$') or regex.endswith('.*')):
regex = regex + '.*'
return "(%s REGEXP %s)" %(key, self.generateValueNode(regex))
else:
raise TypeError("Backend does not support map values of type " + str(type(value)))
def generateMapItemListNode(self, key, value):
equallist = list()
containlist = list()
regexlist = list()
for item in value:
if type(item) == str and "*" in item[1:-1]:
item = re.sub('([".^$]|\\\\(?![*?]))', '\\\\\g<1>', item)
item = re.sub('\\*', '.*', item)
item = re.sub('\\?', '.', item)
regexlist.append(self.generateValueNode(item))
elif type(item) == str and (item.endswith("*") or item.startswith("*")):
item_temp=item
item = re.sub("(\*\\\\)|(\*)", "", item)
if item_temp.endswith("*") and item_temp.startswith("*"): # pattern begins with "*" and ends with "*"
containlist.append(self.generateValueNode('%'+item+'%')) # add "%" to construct the like expression ex: process like %psexesvc%
elif item_temp.startswith("*"): # pattern don't end with "*"
containlist.append(self.generateValueNode('%'+item))
else: # item_temp.endswith("*") pattern don't begin with "*"
containlist.append(self.generateValueNode(item+'%'))
else:
equallist.append(self.generateValueNode(item))
fmtitems = list()
if equallist:
if len(equallist) == 1:
fmtitems.append("%s = %s" % (key, ", ".join(equallist)))
else:
# add "(" and ")" to the first and the last item from the list to have meta_key IN ('value1','value2')
equallist[0]=("("+equallist[0])
equallist[-1]=(equallist[-1]+")")
fmtitems.append("%s IN %s" % (key, ", ".join(equallist)))
if containlist:
fmtitems.append("%s LIKE %s" % (key, (" OR "+key+" LIKE ").join(containlist)))
if regexlist:
fmtitems.append("%s REGEXP %s" % (key, "|".join(regexlist)))
fmtquery = "("+" OR ".join(filter(None, fmtitems))
# Delete the " ' " from the begin or the end of each regex pattern ex : '.*('patern1'|'patern2').*' --> '.*(patern1|patern2).*'
fmtquery = re.sub('\'\.\*\(\'','\'.*(',fmtquery)
fmtquery = re.sub('\'\)\.\*\'',').*\'',fmtquery)
fmtquery = re.sub('\'\|\'','|',fmtquery)
fmtquery = fmtquery+')'
return fmtquery
def generateValueNode(self, node):
return self.valueExpression % (str(node))
def generate(self, sigmaparser):
"""Method is called for each sigma rule and receives the parsed rule (SigmaParser)"""
for parsed in sigmaparser.condparsed:
query = self.generateQuery(parsed, sigmaparser)
query=query.replace('INDEX','`index`') # index is reserved keyword in Esper and must be escaped
query=template.replace('EXPRESSION', query)
try:
query=query.replace('RuleName', sigmaparser.parsedyaml["title"].replace(" ","")) # add rule name
query=query.replace('module_XXXXX', "module "+sigmaparser.parsedyaml["title"].replace(" ","")) # add rule name
except:
print("Error when replacing RuleName by Title from yaml")
pass
return query
def generateQuery(self, parsed, sigmaparser):
result = self.generateNode(parsed.parsedSearch)
return result

View File

@ -0,0 +1,244 @@
import re
import sigma
from sigma.backends.base import SingleTextQueryBackend
from sigma.backends.mixins import MultiRuleOutputMixin
from .exceptions import NotSupportedError
class SysmonConfigBackend(SingleTextQueryBackend, MultiRuleOutputMixin):
identifier = "sysmon"
active = True
andToken = " AND "
orToken = " OR "
notToken = "NOT "
subExpression = "(%s)"
config_required = False
INCLUDE = "include"
EXCLUDE = "exclude"
conditionDict = {
"startswith": "begin with",
"endswith": "end with",
}
def __init__(self, *args, **kwargs):
self.table = None
self.logsource = None
self.allowedSource = {
"process_creation": "ProcessCreate"
}
self.eventidTagMapping = {
1: "ProcessCreate",
4799: "ProcessCreate",
2: "FileCreateTime",
3: "NetworkConnect",
5: "ProcessTerminate",
6: "DriverLoad",
7: "ImageLoad",
8: "CreateRemoteThread",
9: "RawAccessRead",
10: "ProcessAccess",
11: "FileCreate",
12: "RegistryEvent",
13: "RegistryEvent",
14: "RegistryEvent",
15: "FileCreateStreamHash",
17: "PipeEvent",
18: "PipeEvent",
19: "WmiEvent",
20: "WmiEvent",
21: "WmiEvent",
22: "DNSQuery",
257: "DNSQuery",
23: "FileDelete"
}
self.allowedCondCombinations = {
'single': [
[4],
[1, 4],
[2, 4],
],
'multi': [
[1, 2, 4],
],
"exclude": [
[1, 3, 4],
[2, 3, 4]
],
# "multi-exclude": [
# [1, 2, 3, 4]
# ]
}
return super().__init__(*args, **kwargs)
def cleanValue(self, value):
val = re.sub("[*]", "", value)
return val
def mapFiledValue(self, field, value):
condition = None
if "|" in field:
field, *pipes = field.split("|")
if len(pipes) == 1:
condition = pipes[0]
else:
raise NotImplementedError("not implemented condition")
if isinstance(value, list) and len(value) > 1:
condition = "contains any"
value = ";".join(value)
elif "*" in value:
if value.startswith("*") and value.endswith("*"):
condition = "contains"
elif value.startswith("*"):
condition = "end with"
elif value.endswith("*"):
condition = "begin with"
else:
condition = "contains"
if condition:
field_str = '<{field} condition="{condition}">{value}</{field}>'.format(field=field,
condition=condition,
value=self.cleanValue(value))
else:
field_str = '<{field}>{value}</{field}>'.format(field=field, value=self.cleanValue(value))
return field_str
def createRule(self, selections):
fields_list = []
table = None
for field, value in selections.items():
if isinstance(value, list) and len(value) == 1:
value = value[0]
if field == "EventID":
try:
table = self.eventidTagMapping[value]
except KeyError:
table = self.eventidTagMapping[1]
else:
created_field_value = self.mapFiledValue(field, value)
fields_list.append(created_field_value)
fields_list_filtered = [item for item in fields_list if item]
if any(fields_list_filtered):
rule = '''\n\t\t<Rule name="{rule_name}" groupRelation="and">\n\t\t\t{fields}\n\t\t</Rule>'''.format(rule_name=self.rule_name, fields="\n\t\t\t".join(["{}".format(item) for item in fields_list_filtered]))
t = table if table else self.table
return rule, t
else:
return None, None
def createRuleGroup(self, condition_objects, condition, match_type="include"):
rules = None
rules_selections = [item for item in condition_objects if item.type == 4]
if len(rules_selections) == 1:
rule, table = self.createRule(self.detection.get(rules_selections[0].matched))
rules = {match_type: {table: rule}}
else:
if "or" in condition.lower():
result = {}
for selection_object in rules_selections:
rule, table = self.createRule(self.detection.get(selection_object.matched))
if result.get(table):
result[table].append(rule)
else:
result[table] = [rule]
result = {table_name: "\n\t\t".join(rules_list) for table_name, rules_list in result.items()}
rules = {match_type: result}
elif "and" in condition.lower():
rules_dict = {}
for selection_object in rules_selections:
rules_dict.update(self.detection.get(selection_object.matched))
rule, table = self.createRule(rules_dict)
rules = {match_type: {table: rule}}
if rules:
rules_result = []
for match, tables in rules.items():
for table, rules in tables.items():
category_comment = '\n<!--Insert This Rule in <{} onmatch="{}"> section -->\n{}'.format(table, match,
"".join(rules))
rules_result.append(category_comment)
return "".join(rules_result)
else:
raise NotSupportedError("Couldn't create rule with current condition.")
def createMultiRuleGroup(self, conditions):
conditions_id = "".join([str(item.type) for item in conditions])
or_index = conditions_id.index("2")
sorted_conditions = [conditions[:or_index], conditions[or_index+1:]]
if sorted_conditions:
result = ""
for rule_condition in sorted_conditions:
rule = self.createRuleGroup(condition_objects=rule_condition, condition=" ".join([item.matched for item in rule_condition]))
result += "{}\n".format(rule)
return result
else:
raise NotSupportedError("Not implemented condition.")
def createExcludeRuleGroup(self, conditions):
conditions_id = "".join([str(item.type) for item in conditions])
condition = self.detection.get("condition")
sorted_conditions = None
if "and not" in condition.lower():
andnot_index = conditions_id.index("13")
sorted_conditions = [(conditions[:andnot_index], self.INCLUDE), ([item for item in conditions if item.type != 3], self.EXCLUDE)]
elif "or not" in condition.lower():
ornot_index = conditions_id.index("23")
sorted_conditions = [(conditions[:ornot_index], self.INCLUDE), (conditions[ornot_index + 2:], self.EXCLUDE)]
if sorted_conditions:
result = ""
for rule_condition in sorted_conditions:
rule = self.createRuleGroup(condition_objects=rule_condition[0], condition=" ".join([item.matched for item in rule_condition[0]]), match_type=rule_condition[1])
result += "{}\n".format(rule)
return result
def checkRuleCondition(self, condtokens):
if len(condtokens) == 1:
conditions = [item for item in condtokens[0].tokens]
conditions_combination = list(set([item.type for item in conditions]))
for rule_type, combinations in self.allowedCondCombinations.items():
for combination in combinations:
if sorted(conditions_combination) == sorted(combination):
return rule_type, conditions
else:
raise NotSupportedError("Not supported condition.")
else:
raise NotSupportedError("Not supported condition.")
def createTableFromLogsource(self):
if self.logsource.get("product", "") != "windows":
raise NotSupportedError(
"Not supported logsource. Should be product `windows`.")
for item in self.logsource.values():
if item.lower() in self.allowedSource.keys():
self.table = self.allowedSource.get(item.lower())
break
else:
self.table = "ProcessCreate"
def checkDetection(self):
for selection_name, value in self.detection.items():
if isinstance(value, list):
raise NotSupportedError("Keywords are not supported in sysmon backend.")
def generate(self, sigmaparser):
sysmon_rule = None
title = sigmaparser.parsedyaml.get("title", "")
author = sigmaparser.parsedyaml.get("author", {})
self.rule_name = "{} by {}".format(title, author)
self.detection = sigmaparser.parsedyaml.get("detection", {})
self.checkDetection()
self.logsource = sigmaparser.parsedyaml["logsource"]
self.createTableFromLogsource()
rule_type, conditions = self.checkRuleCondition(sigmaparser.condtoken)
if rule_type == "single":
sysmon_rule = self.createRuleGroup(conditions, self.detection.get("condition"))
elif rule_type == "multi":
sysmon_rule = self.createMultiRuleGroup(conditions)
elif rule_type == "exclude":
sysmon_rule = self.createExcludeRuleGroup(conditions)
if sysmon_rule:
rulegroup_comment = '<!--RuleGroup groupRelation should be `or` <RuleGroup groupRelation="or"> -->'
return "{}\n{}".format(rulegroup_comment, sysmon_rule)