Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f60e7e125f | |||
| 7647587a8b | |||
| de2ed08695 | |||
| a1c32123f1 | |||
| e411039b56 | |||
| ae6df590a9 | |||
| 49877a6ed0 | |||
| 3c1c9d2b31 | |||
| 8420d3174a | |||
| c637c2e590 | |||
| 9b8df865b1 | |||
| a4fb39a336 | |||
| 169a4404c2 | |||
| 6d293d498d | |||
| cf237cf658 | |||
| d8bbf26f2c | |||
| 15a6c5efb5 | |||
| aeda30a389 | |||
| 58517907ad | |||
| 0ffd226293 | |||
| 52d405bb1b | |||
| ef7fb4cff1 | |||
| b065c2c35c | |||
| fa6677a41d | |||
| be3c27981f | |||
| 788111f174 | |||
| 56172ae174 | |||
| a9c7fe202e | |||
| 8ddd40e18e | |||
| e53826e167 | |||
| 6eb8cdfeab | |||
| 05928d4f8f | |||
| f113832c04 | |||
| 35d43c5ed9 | |||
| 69671733a8 | |||
| 0b3b0c3aaf | |||
| 24d94d39b8 | |||
| 4183b1b59e | |||
| 22ee6f4521 | |||
| 17c1c1adff | |||
| a3e02ea70f | |||
| b1bfa64231 | |||
| f68af2a5da | |||
| dacc6ae3d3 | |||
| e141a834ff | |||
| c10da5b734 | |||
| a797a281ac | |||
| 3962520848 | |||
| 5f8b60cc24 | |||
| f220e61adc | |||
| 70c2f973a3 | |||
| 3c968d4ec6 | |||
| 5c0f811f4a | |||
| 0018503501 | |||
| 7360a68741 | |||
| 4a9849b161 | |||
| bd20ffdad9 | |||
| 177e2acf8e | |||
| 97204d8dc0 | |||
| e9fcfcba7f | |||
| a7eb4d3e34 | |||
| b84bbd327b | |||
| a6d293e31d | |||
| 8fb6bc7a8a | |||
| af8be8f064 | |||
| 648ac5a52e | |||
| 3f5f3a8d50 | |||
| f6858c436a | |||
| 578118315c | |||
| e162ba0155 | |||
| ff45901ea3 | |||
| 49c12f1df8 | |||
| a257b7d9d7 | |||
| 8b31767d31 | |||
| 0460e7f18a | |||
| f5494c6f5f | |||
| d9d27fec74 | |||
| d8bd65f9ff | |||
| 13ec4c3e3b | |||
| 74c2f91a7d | |||
| 66d52cfeef | |||
| ef75f2a248 | |||
| e9d16bfae1 | |||
| 5ae5c9de19 | |||
| 6a65a7a1bf | |||
| aff46be8a3 | |||
| ada1ca94ea | |||
| 8ee24bf150 | |||
| 1dc3ae1a8e | |||
| 54d9e52527 | |||
| 3b8b04fe09 | |||
| 8041f77abd | |||
| 84645f4e59 | |||
| 7141729ffc | |||
| b9102d0b0a | |||
| 1ecfd83a6a | |||
| 6b69f423da | |||
| 17e8f06161 | |||
| 00177560ca |
+5
-1
@@ -3,9 +3,13 @@ python:
|
||||
- 3.5
|
||||
- 3.6
|
||||
- pypy3
|
||||
services:
|
||||
- elasticsearch
|
||||
cache: pip
|
||||
before_install:
|
||||
- curl -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.2.4.deb && sudo dpkg -i --force-confnew elasticsearch-6.2.4.deb && sudo service elasticsearch restart
|
||||
install:
|
||||
- pip install -r tools/requirements-devel.txt
|
||||
|
||||
script:
|
||||
- make test
|
||||
- make test-backend-es-qs
|
||||
|
||||
@@ -21,6 +21,7 @@ test-sigmac:
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t xpack-watcher rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunk rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t logpoint rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t es-dsl rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunk -f 'level>=high,level<=critical,status=stable,logsource=windows' rules/ > /dev/null
|
||||
! coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunk -f 'level>=high,level<=critical,status=xstable,logsource=windows' rules/ > /dev/null
|
||||
! coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunk -f 'level>=high,level<=xcritical,status=stable,logsource=windows' rules/ > /dev/null
|
||||
@@ -30,7 +31,9 @@ test-sigmac:
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -c tools/config/elk-windows.yml -t es-qs rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -c tools/config/elk-linux.yml -t es-qs rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -c tools/config/elk-windows.yml -t kibana rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -c tools/config/elk-windows.yml -Ooutput=curl -t kibana rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -c tools/config/elk-linux.yml -t kibana rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -c tools/config/elk-linux.yml -Ooutput=curl -t kibana rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -c tools/config/elk-windows.yml -t xpack-watcher rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -c tools/config/elk-linux.yml -t xpack-watcher rules/ > /dev/null
|
||||
coverage run -a --include=$(COVSCOPE) tools/sigmac -rvdI -c tools/config/elk-defaultindex.yml -t xpack-watcher rules/ > /dev/null
|
||||
@@ -57,7 +60,10 @@ test-sigmac:
|
||||
|
||||
test-merge:
|
||||
tests/test-merge.sh
|
||||
! coverage run -a --include=$(COVSCOPE) tools/merge_sigma.py tests/not_existing.yml > /dev/null
|
||||
! coverage run -a --include=$(COVSCOPE) tools/merge_sigma tests/not_existing.yml > /dev/null
|
||||
|
||||
test-backend-es-qs:
|
||||
tests/test-backend-es-qs.py
|
||||
|
||||
build: tools/sigmac tools/merge_sigma tools/sigma/*.py tools/setup.py tools/setup.cfg
|
||||
cd tools && python3 setup.py bdist_wheel
|
||||
|
||||
@@ -18,6 +18,8 @@ This repository contains:
|
||||
* Open repository for sigma signatures in the `./rules`subfolder
|
||||
* A converter that generate searches/queries for different SIEM systems [work in progress]
|
||||
|
||||

|
||||
|
||||
## Hack.lu 2017 Talk
|
||||
|
||||
[](https://www.youtube.com/watch?v=OheVuE9Ifhs "Sigma - Generic Signatures for Log Events")
|
||||
@@ -32,22 +34,6 @@ This repository contains:
|
||||
* Write a rule converter for your custom log analysis tool and process new Sigma rules automatically
|
||||
* Provide a free or commercial feed for Sigma signatures
|
||||
|
||||
# Sigma Converter
|
||||
|
||||
The converter is currently under development in the *devel-sigmac* branch of this project. It has currently the
|
||||
following capabilities:
|
||||
|
||||
* Parsing of Sigma rule files
|
||||
* Conversion of searches into Elasticsearch and Splunk queries
|
||||
|
||||
Planned main features are:
|
||||
|
||||
* Conversion of aggregation expressions (after the pipe character)
|
||||
* Output of Kibana JSON configurations
|
||||
|
||||
Support for further SIEM solutions can be added by developing an corresponsing output backend class.
|
||||
|
||||

|
||||
|
||||
# Why Sigma
|
||||
|
||||
@@ -94,7 +80,7 @@ Sysmon: Web Shell Detection
|
||||
Windows 'Security' Eventlog: Suspicious Number of Failed Logons from a Single Source Workstation
|
||||

|
||||
|
||||
## Sigma Toolchain
|
||||
## Sigma Tools
|
||||
|
||||
Sigmac converts sigma rules into queries or inputs of the supported targets listed below. It acts as a frontend to the
|
||||
Sigma library that may be used to integrate Sigma support in other projects. Further, there's `merge_sigma.py` which
|
||||
@@ -105,9 +91,13 @@ merges multiple YAML documents of a Sigma rule collection into simple Sigma rule
|
||||
### Supported Targets
|
||||
|
||||
* [Splunk](https://www.splunk.com/)
|
||||
* [ElasticSearch](https://www.elastic.co/)
|
||||
* [Elasticsearch](https://www.elastic.co/)
|
||||
* [Kibana](https://www.elastic.co/de/products/kibana)
|
||||
* [Elastic X-Pack Watcher](https://www.elastic.co/guide/en/x-pack/current/xpack-alerting.html)
|
||||
* [Logpoint](https://www.logpoint.com)
|
||||
* Grep with Perl-compatible regular expression support
|
||||
|
||||
New targets are continuously developed. A current list can be obtained with `sigmac --target-list` or `sigmac -l`.
|
||||
|
||||
### Requirements
|
||||
|
||||
@@ -121,6 +111,27 @@ It's available on PyPI. Install with:
|
||||
pip3 install sigmatools
|
||||
```
|
||||
|
||||
Alternatively, if used from the Sigma Github repository, the Python dependencies can be installed with:
|
||||
|
||||
```bash
|
||||
pip3 install -r tools/requirements.txt
|
||||
```
|
||||
|
||||
For development (e.g. execution of integration tests with `make` and packaging), further dependencies are required and can be installed with:
|
||||
|
||||
```bash
|
||||
pip3 install -r tools/requirements-devel.txt
|
||||
```
|
||||
|
||||
## Contributed Scripts
|
||||
|
||||
The directory `contrib` contains scripts that were contributed by the community:
|
||||
|
||||
* `sigma2elastalert.py`i by David Routin: A script that converts Sigma rules to Elastalert configurations. This tool
|
||||
uses *sigmac* and expects it in its path.
|
||||
|
||||
These tools are not part of the main toolchain and maintained separately by their authors.
|
||||
|
||||
# Next Steps
|
||||
|
||||
* Integration of feedback into the rule specifications
|
||||
|
||||
Executable
+173
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright 2018 David Routin
|
||||
|
||||
# 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/>.
|
||||
"""
|
||||
Project: sigma2elastalert.py
|
||||
Date: 25 Feb 2018
|
||||
Author: David ROUTIN (@Rewt_1)
|
||||
Version: 1.0
|
||||
Description: This script creates elastalert configuration files from Sigma SIEM rules.
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
import glob
|
||||
import subprocess
|
||||
import argparse
|
||||
import yaml
|
||||
import traceback
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--eshost", help="Elasticsearch host", type=str, required=True)
|
||||
parser.add_argument("--esport", help="Elasticsearch port", type=str, required=True)
|
||||
parser.add_argument("--ruledir", help="sigma rule directory path to convert", type=str, required=True)
|
||||
parser.add_argument("--index", help="Elasticsearch index name egs: \"winlogbeat-*\"", type=str, required=True)
|
||||
parser.add_argument("--email", help="email address to send mail alert", type=str, required=True)
|
||||
parser.add_argument("--outdir", help="output directory to create elastalert rules", type=str, required=True)
|
||||
parser.add_argument("--sigmac", help="Sigmac location", default="../tools/sigmac", type=str)
|
||||
parser.add_argument("--realerttime", help="Realert time (optional value, default 5 minutes)", type=str, default=5)
|
||||
parser.add_argument("--debug", help="Show debug output", type=bool, default=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
custom_query_keys = ["sensor", "Hostname", "EventID", "src_ip", "dst_ip"]
|
||||
|
||||
|
||||
template="""es_host: ESHOST
|
||||
es_port: ESPORT
|
||||
name: "TITLE"
|
||||
description: "DESCRIPTION"
|
||||
index: INDEX
|
||||
filter:
|
||||
- query:
|
||||
query_string:
|
||||
query: 'QUERY'
|
||||
realert:
|
||||
minutes: MINUTES
|
||||
query_key: UNIQKEYS
|
||||
type: any
|
||||
include: UNIQKEYS
|
||||
alert:
|
||||
- "email"
|
||||
|
||||
# (required, email specific)
|
||||
# a list of email addresses to send alerts to
|
||||
email:
|
||||
- "EMAIL"
|
||||
"""
|
||||
|
||||
def return_json_obj(x,custom_query_keys):
|
||||
"""
|
||||
Function used to filter all ES query object as unique value including predefined list from custom_query_keys
|
||||
:param x: must contains ES query output
|
||||
:param custom_query_keys: takes the list of predefined element to match in document
|
||||
:return: a clean list (set) of all the query keys (EventID,TargetUserName...)
|
||||
"""
|
||||
# type: (str, list) -> list
|
||||
y = x.replace(" ", "\n").split()
|
||||
out = set()
|
||||
for i in y:
|
||||
out.update(re.findall("([a-zA-Z]+)\:", i))
|
||||
|
||||
for qk in custom_query_keys:
|
||||
try:
|
||||
out.remove(qk)
|
||||
except:
|
||||
pass
|
||||
out = list(out)
|
||||
count = 0
|
||||
for qk in custom_query_keys:
|
||||
count += 1
|
||||
out.insert(count-1, qk)
|
||||
return out
|
||||
|
||||
def rule_element(file_content, elements):
|
||||
"""
|
||||
Function used to get specific element from yaml document and return content
|
||||
:type file_content: str
|
||||
:type elements: list
|
||||
:param file_content:
|
||||
:param elements: list of elements of the yaml document to get "title", "description"
|
||||
:return: the value of the key in the yaml document
|
||||
"""
|
||||
try:
|
||||
yaml.load(file_content.replace("---",""))
|
||||
except:
|
||||
raise Exception('Unsupported')
|
||||
element_output = ""
|
||||
for e in elements:
|
||||
try:
|
||||
element_output = yaml.load(file_content.replace("---",""))[e]
|
||||
except:
|
||||
pass
|
||||
if element_output is None:
|
||||
return ""
|
||||
return element_output
|
||||
|
||||
def get_rule_as_esqs(file):
|
||||
"""
|
||||
Function used to get Elastic query output from rule fome
|
||||
:type file: str
|
||||
:param file: rule filename
|
||||
:return: string es query
|
||||
"""
|
||||
if not os.path.exists(args.sigmac):
|
||||
print("Cannot find sigmac rule coverter at '%s', please set a correct location via '--sigmac'")
|
||||
cmd = [args.sigmac, file, "--target", "es-qs"]
|
||||
output = subprocess.Popen(cmd,stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read()
|
||||
if "unsupported" in output:
|
||||
raise Exception('Unsupported output at this time')
|
||||
output = output.split("\n")
|
||||
# Remove empty string from \n
|
||||
output = [a for a in output if a]
|
||||
# Handle case of multiple queries returned
|
||||
if len(output) > 1:
|
||||
return " OR ".join(output)
|
||||
return "".join(output)
|
||||
|
||||
# Dictionary that contains args set at launch time
|
||||
convert_args = {
|
||||
"ESHOST": args.eshost,
|
||||
"ESPORT": args.esport,
|
||||
"INDEX": args.index,
|
||||
"EMAIL": args.email,
|
||||
"MINUTES": args.realerttime
|
||||
}
|
||||
|
||||
for file in glob.glob(args.ruledir + "/*"):
|
||||
output_elast_config = template
|
||||
try:
|
||||
print("Processing %s ..." % file)
|
||||
with open(file, "rb") as f:
|
||||
file_content = f.read()
|
||||
|
||||
# Dictionary that contains args with values returned by functions
|
||||
translate_func = {'QUERY': get_rule_as_esqs(file),
|
||||
'TITLE': rule_element(file_content, ["title", "name"]),
|
||||
'DESCRIPTION': rule_element(file_content, ["description"]),
|
||||
'UNIQKEYS': str(return_json_obj(get_rule_as_esqs(file), custom_query_keys))
|
||||
}
|
||||
for entry in convert_args:
|
||||
output_elast_config = re.sub(entry, str(convert_args[entry]), output_elast_config)
|
||||
for entry in translate_func:
|
||||
output_elast_config = re.sub(entry, translate_func[entry], output_elast_config)
|
||||
print "Converting file " + file
|
||||
with open(os.path.join(args.outdir, "sigma-" + file.split("/")[-1]), "w") as f:
|
||||
f.write(output_elast_config)
|
||||
except Exception as e:
|
||||
if args.debug:
|
||||
traceback.print_exc()
|
||||
print "error " + str(file) + "----" + str(e)
|
||||
pass
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 43 KiB |
@@ -16,7 +16,7 @@ detection:
|
||||
# SQL Server
|
||||
- Unclosed quotation mark
|
||||
# SQLite
|
||||
- near "*": syntax error
|
||||
- 'near "*": syntax error'
|
||||
- SELECTs to the left and right of UNION do not have the same number of result columns
|
||||
condition: keywords
|
||||
falsepositives:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
---
|
||||
action: global
|
||||
title: APT29 Google Update Service Install
|
||||
description: 'This method detects malicious services mentioned in APT29 report by FireEye. The legitimate path for the Google update service is C:\Program Files (x86)\Google\Update\GoogleUpdate.exe so the service names and executable locations used by APT29 are specific enough to be detected in log files.'
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
action: global
|
||||
title: Chafer Activity
|
||||
description: Detects Chafer activity attributed to OilRig as reported in Nyotron report in March 2018
|
||||
references:
|
||||
- https://nyotron.com/nyotron-discovers-next-generation-oilrig-attacks/
|
||||
date: 2018/03/23
|
||||
author: Florian Roth, Markus Neis
|
||||
detection:
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: critical
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: system
|
||||
detection:
|
||||
selection_service:
|
||||
EventID: 7045
|
||||
ServiceName:
|
||||
- 'SC Scheduled Scan'
|
||||
- 'UpdatMachine'
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection_reg1:
|
||||
EventID: 13
|
||||
TargetObject:
|
||||
- '*SOFTWARE\Microsoft\Windows\CurrentVersion\UMe'
|
||||
- '*SOFTWARE\Microsoft\Windows\CurrentVersion\UT'
|
||||
EventType: 'SetValue'
|
||||
selection_reg2:
|
||||
EventID: 13
|
||||
TargetObject: '*\Control\SecurityProviders\WDigest\UseLogonCredential'
|
||||
EventType: 'SetValue'
|
||||
Details: 'DWORD (0x00000001)'
|
||||
selection_process1:
|
||||
EventID: 1
|
||||
CommandLine:
|
||||
- '*\Service.exe i'
|
||||
- '*\Service.exe u'
|
||||
- '*\microsoft\Taskbar\autoit3.exe'
|
||||
- 'C:\wsc.exe*'
|
||||
selection_process2:
|
||||
EventID: 1
|
||||
Image: '*\Windows\Temp\DB\*.exe'
|
||||
selection_process3:
|
||||
EventID: 1
|
||||
CommandLine: '*\nslookup.exe -q=TXT*'
|
||||
ParentImage: '*\Autoit*'
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
action: global
|
||||
title: CrackMapExecWin
|
||||
description: Detects CrackMapExecWin Activity as Described by NCSC
|
||||
status: experimental
|
||||
references:
|
||||
- https://www.ncsc.gov.uk/alerts/hostile-state-actors-compromising-uk-organisations-focus-engineering-and-industrial-control
|
||||
author: Markus Neis
|
||||
detection:
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- None
|
||||
level: critical
|
||||
---
|
||||
# Windows Audit Log
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
description: 'Requirements: Audit Policy : Detailed Tracking > Audit Process creation, Group Policy : Administrative Templates\System\Audit Process Creation'
|
||||
detection:
|
||||
selection1:
|
||||
# Does not require group policy 'Audit Process Creation' > Include command line in process creation events
|
||||
EventID: 4688
|
||||
NewProcessName:
|
||||
- '*\crackmapexec.exe'
|
||||
---
|
||||
# Sysmon
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection1:
|
||||
# Does not require group policy 'Audit Process Creation' > Include command line in process creation events
|
||||
EventID: 1
|
||||
Image:
|
||||
- '*\crackmapexec.exe'
|
||||
@@ -16,7 +16,7 @@ detection:
|
||||
selection2:
|
||||
EventID: 1
|
||||
CommandLine: '*\AppData\Roaming\MICROS~1\Windows\Caches\NavShExt.dll,Setting'
|
||||
condition: selection1 or selection2
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: critical
|
||||
|
||||
@@ -15,7 +15,7 @@ detection:
|
||||
src:
|
||||
- '69.42.98.86'
|
||||
- '89.185.234.145'
|
||||
condition: outgoing or incoming
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: high
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
action: global
|
||||
title: Equation Group DLL_U Load
|
||||
description: Detects a specific tool and export used by EquationGroup
|
||||
references:
|
||||
- https://github.com/adamcaudill/EquationGroupLeak/search?utf8=%E2%9C%93&q=dll_u&type=
|
||||
- https://securelist.com/apt-slingshot/84312/
|
||||
- https://twitter.com/cyb3rops/status/972186477512839170
|
||||
author: Florian Roth
|
||||
date: 2018/03/10
|
||||
detection:
|
||||
selection1:
|
||||
Image: '*\rundll32.exe'
|
||||
CommandLine: '*,dll_u'
|
||||
selection2:
|
||||
CommandLine: '* -export dll_u *'
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: critical
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection1:
|
||||
EventID: 1
|
||||
selection2:
|
||||
EventID: 1
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
description: 'Requirements: Audit Policy : Detailed Tracking > Audit Process creation, Group Policy : Administrative Templates\System\Audit Process Creation'
|
||||
detection:
|
||||
selection1:
|
||||
EventID: 4688
|
||||
selection2:
|
||||
EventID: 4688
|
||||
@@ -18,7 +18,7 @@ detection:
|
||||
selection2:
|
||||
EventID: 1
|
||||
Command: 'loaddll -a *'
|
||||
condition: selection1 or selection2
|
||||
condition: 1 of them
|
||||
fields:
|
||||
- EventID
|
||||
- CommandLine
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
action: global
|
||||
title: Defrag Deactivation
|
||||
description: Detects the deactivation of the Scheduled defragmentation task as seen by Slingshot APT group
|
||||
references:
|
||||
- https://securelist.com/apt-slingshot/84312/
|
||||
author: Florian Roth
|
||||
date: 2018/03/10
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
description: 'Requirements: Audit Policy : Audit Other Object Access Events > Success'
|
||||
detection:
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: medium
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
CommandLine:
|
||||
- '*schtasks* /delete *Defrag\ScheduledDefrag*'
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
description: 'Requirements: Audit Policy : Audit Other Object Access Events > Success'
|
||||
detection:
|
||||
selection:
|
||||
EventID: 4701
|
||||
TaskName: '\Microsoft\Windows\Defrag\ScheduledDefrag'
|
||||
@@ -0,0 +1,21 @@
|
||||
title: Windows PowerShell WebDav User Agent
|
||||
status: experimental
|
||||
description: Detects Windows PowerShell Web Access
|
||||
references:
|
||||
- https://mgreen27.github.io/posts/2018/04/02/DownloadCradle.html
|
||||
author: Florian Roth
|
||||
date: 2018/04/06
|
||||
logsource:
|
||||
category: proxy
|
||||
detection:
|
||||
selection:
|
||||
UserAgent: 'Microsoft-WebDAV-MiniRedir/*'
|
||||
condition: selection
|
||||
fields:
|
||||
- ClientIP
|
||||
- URL
|
||||
- UserAgent
|
||||
falsepositives:
|
||||
- Administrative scripts that download files from the Internet
|
||||
- Administrative scripts that retrieve certain website contents
|
||||
level: high
|
||||
@@ -28,6 +28,8 @@ detection:
|
||||
- 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NETCLR 2.0.50727)' # APT17
|
||||
- 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; SV1)' # Bronze Butler - Daserf
|
||||
- 'Mozilla/4.0 (compatible; MSIE 11.0; Windows NT 6.1; SV1)' # Bronze Butler - Daserf
|
||||
- 'Mozilla/4.0 (compatible; MSIE 8.0; Win32)' # TSCookie https://app.any.run/tasks/0996b314-5133-491b-8d23-d431ffdec597
|
||||
- 'Mozilla v5.1 (Windows NT 6.1; rv:6.0.1) Gecko/20100101 Firefox/6.0.1' # Delphi downloader https://www.welivesecurity.com/2018/04/24/sednit-update-analysis-zebrocy/
|
||||
condition: selection
|
||||
fields:
|
||||
- ClientIP
|
||||
|
||||
@@ -13,7 +13,6 @@ detection:
|
||||
EventID: 4624
|
||||
LogonType: 10
|
||||
AuthenticationPackageName: Negotiate
|
||||
Severity: Information
|
||||
AccountName: 'Admin-*'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
|
||||
@@ -11,7 +11,7 @@ detection:
|
||||
EventID: 5140
|
||||
ShareName: Admin$
|
||||
filter:
|
||||
SubjectAccountName: '*$'
|
||||
SubjectUserName: '*$'
|
||||
condition: selection and not filter
|
||||
falsepositives:
|
||||
- Legitimate administrative activity
|
||||
|
||||
@@ -12,7 +12,7 @@ detection:
|
||||
EventID: 4707
|
||||
keywords:
|
||||
- 'SeEnableDelegationPrivilege'
|
||||
condition: selection and keywords
|
||||
condition: all of them
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: high
|
||||
|
||||
@@ -20,7 +20,7 @@ detection:
|
||||
EventID: 5136
|
||||
ObjectClass: 'user'
|
||||
AttributeLDAPDisplayName: 'servicePrincipalName'
|
||||
condition: selection1 or selection2 or selection3
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: high
|
||||
|
||||
@@ -16,7 +16,7 @@ logsource:
|
||||
detection:
|
||||
selection:
|
||||
EventID: 4719
|
||||
Message: 'removed'
|
||||
AuditPolicyChanges: 'removed'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- Unknown
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
title: smbexec.py Service Installation
|
||||
description: Detects the use of smbexec.py tool by detecting a specific service installation
|
||||
author: Omer Faruk Celik
|
||||
date: 2018/03/20
|
||||
references:
|
||||
- https://blog.ropnop.com/using-credentials-to-own-windows-boxes-part-2-psexec-and-services/
|
||||
logsource:
|
||||
product: windows
|
||||
detection:
|
||||
service_installation:
|
||||
EventID: 7045
|
||||
ServiceName: 'BTOBTO'
|
||||
ServiceFileName: '*\execute.bat'
|
||||
condition: service_installation
|
||||
fields:
|
||||
- ServiceName
|
||||
- ServiceFileName
|
||||
falsepositives:
|
||||
- Penetration Test
|
||||
- Unknown
|
||||
level: critical
|
||||
@@ -7,26 +7,26 @@ logsource:
|
||||
detection:
|
||||
selection:
|
||||
EventID: 7045
|
||||
wce:
|
||||
malsvc_wce:
|
||||
ServiceName:
|
||||
- 'WCESERVICE'
|
||||
- 'WCE SERVICE'
|
||||
paexec:
|
||||
malsvc_paexec:
|
||||
ServiceFileName: '*\PAExec*'
|
||||
winexe:
|
||||
malsvc_winexe:
|
||||
ServiceFileName: 'winexesvc.exe*'
|
||||
pwdumpx:
|
||||
malsvc_pwdumpx:
|
||||
ServiceFileName: '*\DumpSvc.exe'
|
||||
wannacry:
|
||||
malsvc_wannacry:
|
||||
ServiceName: 'mssecsvc2.0'
|
||||
persistence:
|
||||
malsvc_persistence:
|
||||
ServiceFileName: '* net user *'
|
||||
others:
|
||||
malsvc_others:
|
||||
ServiceName:
|
||||
- 'pwdump*'
|
||||
- 'gsecdump*'
|
||||
- 'cachedump*'
|
||||
condition: selection and ( wce or paexec or winexe or pwdumpx or wannacry or persistence or others )
|
||||
condition: selection and 1 of malsvc_*
|
||||
falsepositives:
|
||||
- Penetration testing
|
||||
level: critical
|
||||
|
||||
@@ -4,6 +4,7 @@ description: Detects wceaux.dll access while WCE pass-the-hash remote command ex
|
||||
author: Thomas Patzke
|
||||
references:
|
||||
- https://www.jpcert.or.jp/english/pub/sr/ir_research.html
|
||||
- https://jpcertcc.github.io/ToolAnalysisResultSheet
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
action: global
|
||||
title: NetNTLM Downgrade Attack
|
||||
description: Detects post exploitation using NetNTLM downgrade attacks
|
||||
reference:
|
||||
- https://www.optiv.com/blog/post-exploitation-using-netntlm-downgrade-attacks
|
||||
author: Florian Roth
|
||||
date: 2018/03/20
|
||||
detection:
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: critical
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection1:
|
||||
EventID: 13
|
||||
TargetObject:
|
||||
- '*SYSTEM\*ControlSet*\Control\Lsa\lmcompatibilitylevel'
|
||||
- '*SYSTEM\*ControlSet*\Control\Lsa\NtlmMinClientSec'
|
||||
- '*SYSTEM\*ControlSet*\Control\Lsa\RestrictSendingNTLMTraffic'
|
||||
EventType: 'SetValue'
|
||||
---
|
||||
# Windows Security Eventlog: Process Creation with Full Command Line
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
description: 'Requirements: Audit Policy : Object Access > Audit Registry (Success)'
|
||||
detection:
|
||||
selection2:
|
||||
EventID: 4657
|
||||
OperationType: 'Existing registry value modified'
|
||||
ObjectName: '\REGISTRY\MACHINE\SYSTEM\*ControlSet*\Control\Lsa'
|
||||
ObjectValueName:
|
||||
- 'LmCompatibilityLevel'
|
||||
- 'NtlmMinClientSec'
|
||||
- 'RestrictSendingNTLMTraffic'
|
||||
@@ -12,12 +12,12 @@ detection:
|
||||
selection:
|
||||
- EventID: 4624
|
||||
LogonType: '3'
|
||||
LogonProcess: 'NtLmSsp'
|
||||
LogonProcessName: 'NtLmSsp'
|
||||
WorkstationName: '%Workstations%'
|
||||
ComputerName: '%Workstations%'
|
||||
- EventID: 4625
|
||||
LogonType: '3'
|
||||
LogonProcess: 'NtLmSsp'
|
||||
LogonProcessName: 'NtLmSsp'
|
||||
WorkstationName: '%Workstations%'
|
||||
ComputerName: '%Workstations%'
|
||||
filter:
|
||||
|
||||
@@ -14,50 +14,50 @@ detection:
|
||||
# CamMute
|
||||
selection_cammute:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\CamMute.exe'
|
||||
CommandLine: '*\CamMute.exe'
|
||||
filter_cammute:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\Lenovo\Communication Utility\*'
|
||||
CommandLine: '*\Lenovo\Communication Utility\*'
|
||||
|
||||
# Chrome Frame Helper
|
||||
selection_chrome_frame:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\chrome_frame_helper.exe'
|
||||
CommandLine: '*\chrome_frame_helper.exe'
|
||||
filter_chrome_frame:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\Google\Chrome\application\*'
|
||||
CommandLine: '*\Google\Chrome\application\*'
|
||||
|
||||
# Microsoft Device Emulator
|
||||
selection_devemu:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\dvcemumanager.exe'
|
||||
CommandLine: '*\dvcemumanager.exe'
|
||||
filter_devemu:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\Microsoft Device Emulator\*'
|
||||
CommandLine: '*\Microsoft Device Emulator\*'
|
||||
|
||||
# Windows Media Player Gadget
|
||||
selection_gadget:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\Gadget.exe'
|
||||
CommandLine: '*\Gadget.exe'
|
||||
filter_gadget:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\Windows Media Player\*'
|
||||
CommandLine: '*\Windows Media Player\*'
|
||||
|
||||
# HTML Help Workshop
|
||||
selection_hcc:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\hcc.exe'
|
||||
CommandLine: '*\hcc.exe'
|
||||
filter_hcc:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\HTML Help Workshop\*'
|
||||
CommandLine: '*\HTML Help Workshop\*'
|
||||
|
||||
# Hotkey Command Module for Intel Graphics Contollers
|
||||
selection_hkcmd:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\hkcmd.exe'
|
||||
CommandLine: '*\hkcmd.exe'
|
||||
filter_hkcmd:
|
||||
EventID: 4688
|
||||
ProcessCommandLine:
|
||||
CommandLine:
|
||||
- '*\System32\*'
|
||||
- '*\SysNative\*'
|
||||
- '*\SysWowo64\*'
|
||||
@@ -65,10 +65,10 @@ detection:
|
||||
# McAfee component
|
||||
selection_mc:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\Mc.exe'
|
||||
CommandLine: '*\Mc.exe'
|
||||
filter_mc:
|
||||
EventID: 4688
|
||||
ProcessCommandLine:
|
||||
CommandLine:
|
||||
- '*\Microsoft Visual Studio*'
|
||||
- '*\Microsoft SDK*'
|
||||
- '*\Windows Kit*'
|
||||
@@ -76,10 +76,10 @@ detection:
|
||||
# MsMpEng - Microsoft Malware Protection Engine
|
||||
selection_msmpeng:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\MsMpEng.exe'
|
||||
CommandLine: '*\MsMpEng.exe'
|
||||
filter_msmpeng:
|
||||
EventID: 4688
|
||||
ProcessCommandLine:
|
||||
CommandLine:
|
||||
- '*\Microsoft Security Client\*'
|
||||
- '*\Windows Defender\*'
|
||||
- '*\AntiMalware\*'
|
||||
@@ -87,26 +87,26 @@ detection:
|
||||
# Microsoft Security Center
|
||||
selection_msseces:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\msseces.exe'
|
||||
CommandLine: '*\msseces.exe'
|
||||
filter_msseces:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\Microsoft Security Center\*'
|
||||
CommandLine: '*\Microsoft Security Center\*'
|
||||
|
||||
# Microsoft Office 2003 OInfo
|
||||
selection_oinfo:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\OInfoP11.exe'
|
||||
CommandLine: '*\OInfoP11.exe'
|
||||
filter_oinfo:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\Common Files\Microsoft Shared\*'
|
||||
CommandLine: '*\Common Files\Microsoft Shared\*'
|
||||
|
||||
# OLE View
|
||||
selection_oleview:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\OleView.exe'
|
||||
CommandLine: '*\OleView.exe'
|
||||
filter_oleview:
|
||||
EventID: 4688
|
||||
ProcessCommandLine:
|
||||
CommandLine:
|
||||
- '*\Microsoft Visual Studio*'
|
||||
- '*\Microsoft SDK*'
|
||||
- '*\Windows Kit*'
|
||||
@@ -115,10 +115,10 @@ detection:
|
||||
# RC
|
||||
selection_rc:
|
||||
EventID: 4688
|
||||
ProcessCommandLine: '*\OleView.exe'
|
||||
CommandLine: '*\OleView.exe'
|
||||
filter_rc:
|
||||
EventID: 4688
|
||||
ProcessCommandLine:
|
||||
CommandLine:
|
||||
- '*\Microsoft Visual Studio*'
|
||||
- '*\Microsoft SDK*'
|
||||
- '*\Windows Kit*'
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
title: PsExec Service Start
|
||||
description: Detects a PsExec service start
|
||||
author: Florian Roth
|
||||
date: 2018/03/13
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
description: 'Requirements: Audit Policy : Detailed Tracking > Audit Process creation, Group Policy : Administrative Templates\System\Audit Process Creation'
|
||||
detection:
|
||||
selection:
|
||||
EventID: 4688
|
||||
CommandLine: 'C:\Windows\PSEXESVC.exe'
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- Administrative activity
|
||||
level: low
|
||||
@@ -12,7 +12,6 @@ logsource:
|
||||
service: system
|
||||
detection:
|
||||
selection:
|
||||
EventLog: System
|
||||
EventID: 1033
|
||||
condition: selection
|
||||
falsepositives:
|
||||
|
||||
@@ -12,7 +12,6 @@ logsource:
|
||||
service: system
|
||||
detection:
|
||||
selection:
|
||||
- EventLog: System
|
||||
EventID:
|
||||
- 1031
|
||||
- 1032
|
||||
|
||||
@@ -5,15 +5,20 @@ logsource:
|
||||
product: windows
|
||||
service: security
|
||||
detection:
|
||||
selection:
|
||||
selection1:
|
||||
EventID:
|
||||
- 529
|
||||
- 4625
|
||||
- 4776
|
||||
UserName: not null
|
||||
SourceWorkstation: not null
|
||||
WorkstationName: not null
|
||||
selection2:
|
||||
EventID: 4776
|
||||
UserName: not null
|
||||
Workstation: not null
|
||||
timeframe: 24h
|
||||
condition: selection | count(UserName) by SourceWorkstation > 3
|
||||
condition:
|
||||
- selection1 | count(UserName) by WorkstationName > 3
|
||||
- selection2 | count(UserName) by Workstation > 3
|
||||
falsepositives:
|
||||
- Terminal servers
|
||||
- Jump servers
|
||||
|
||||
@@ -16,11 +16,10 @@ detection:
|
||||
selection2:
|
||||
Source: 'Windows Error Reporting'
|
||||
EventID: 1001
|
||||
keyword1:
|
||||
keywords:
|
||||
- 'MsMpEng.exe'
|
||||
keyword2:
|
||||
- 'mpengine.dll'
|
||||
condition: (selection1 or selection2) and keyword1 and keyword2
|
||||
condition: 1 of selection* and all of keywords
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: high
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
action: global
|
||||
title: Invocation of Active Directory Diagnostic Tool (ntdsutil.exe)
|
||||
description: Detects execution of ntdsutil.exe, which can be used for various attacks against the NTDS database (NTDS.DIT)
|
||||
status: experimental
|
||||
references:
|
||||
- https://jpcertcc.github.io/ToolAnalysisResultSheet/details/ntdsutil.htm
|
||||
author: Thomas Patzke
|
||||
detection:
|
||||
selection:
|
||||
CommandLine: '*\ntdsutil.exe *'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- NTDS maintenance
|
||||
level: high
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
description: 'Requirements: Audit Policy : Detailed Tracking > Audit Process creation, Group Policy : Administrative Templates\System\Audit Process Creation'
|
||||
detection:
|
||||
selection:
|
||||
EventID: 4688
|
||||
|
||||
@@ -2,7 +2,8 @@ title: Suspicious Kerberos RC4 Ticket Encryption
|
||||
status: experimental
|
||||
references:
|
||||
- https://adsecurity.org/?p=3458
|
||||
description: Detects logons using RC4 encryption type
|
||||
- https://www.trimarcsecurity.com/single-post/TrimarcResearch/Detecting-Kerberoasting-Activity
|
||||
description: Detects service ticket requests using RC4 encryption type
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
@@ -10,10 +11,9 @@ detection:
|
||||
selection:
|
||||
EventID: 4769
|
||||
TicketOptions: '0x40810000'
|
||||
TicketEncryption: '0x17'
|
||||
TicketEncryptionType: '0x17'
|
||||
reduction:
|
||||
- ServiceName: '$*'
|
||||
- Type: 'Success Audit'
|
||||
condition: selection and not reduction
|
||||
falsepositives:
|
||||
- Service accounts used on legacy systems (e.g. NetApp)
|
||||
|
||||
@@ -11,7 +11,7 @@ detection:
|
||||
EventID: 16
|
||||
keywords:
|
||||
- '*\AppData\Local\Temp\SAM-*.dmp *'
|
||||
condition: selection and keywords
|
||||
condition: all of them
|
||||
falsepositives:
|
||||
- Penetration testing
|
||||
level: high
|
||||
|
||||
@@ -6,11 +6,9 @@ logsource:
|
||||
service: security
|
||||
detection:
|
||||
samrpipe:
|
||||
- EventLog: Security
|
||||
EventID: 5145
|
||||
RelativeTargetName: samr
|
||||
passwordchanged:
|
||||
- EventLog: Security
|
||||
EventID: 4738
|
||||
PasswordLastSet: (any)
|
||||
timeframe: 15s
|
||||
|
||||
@@ -3,6 +3,7 @@ status: experimental
|
||||
description: Detects renaming of file while deletion with SDelete tool
|
||||
author: Thomas Patzke
|
||||
references:
|
||||
- https://jpcertcc.github.io/ToolAnalysisResultSheet
|
||||
- https://www.jpcert.or.jp/english/pub/sr/ir_research.html
|
||||
- https://technet.microsoft.com/en-us/en-en/sysinternals/sdelete.aspx
|
||||
logsource:
|
||||
|
||||
@@ -10,7 +10,7 @@ detection:
|
||||
EventID: 4732
|
||||
GroupName: Administrators
|
||||
filter:
|
||||
SubjectAccountName: '*$'
|
||||
SubjectUserName: '*$'
|
||||
condition: selection and not filter
|
||||
falsepositives:
|
||||
- Legitimate administrative activity
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
action: global
|
||||
title: WMI Persistence - Script Event Consumer
|
||||
status: experimental
|
||||
description: Detects WMI script event consumers
|
||||
references:
|
||||
- https://www.eideon.com/2018-03-02-THL03-WMIBackdoors/
|
||||
author: Thomas Patzke
|
||||
date: 2018/03/07
|
||||
detection:
|
||||
selection:
|
||||
Image: 'C:\WINDOWS\system32\wbem\scrcons.exe'
|
||||
ParentImage: 'C:\Windows\System32\svchost.exe'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- Legitimate event consumers
|
||||
level: high
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
description: 'Requirements: Audit Policy : Detailed Tracking > Audit Process creation, Group Policy : Administrative Templates\System\Audit Process Creation'
|
||||
detection:
|
||||
selection:
|
||||
EventID: 4688
|
||||
@@ -26,7 +26,7 @@ detection:
|
||||
CommandLine: '*.dat,#1'
|
||||
perfc_keyword:
|
||||
- '*\perfc.dat*'
|
||||
condition: fsutil_clean_journal or pipe_com or event_clean or rundll32_dash1 or perfc_keyword
|
||||
condition: 1 of them
|
||||
fields:
|
||||
- CommandLine
|
||||
- ParentCommandLine
|
||||
|
||||
@@ -30,7 +30,7 @@ detection:
|
||||
- '*bcdedit /set {default} recoveryenabled no*'
|
||||
- '*wbadmin delete catalog -quiet*'
|
||||
- '*@Please_Read_Me@.txt*'
|
||||
condition: selection1 or selection2
|
||||
condition: 1 of them
|
||||
fields:
|
||||
- CommandLine
|
||||
- ParentCommandLine
|
||||
|
||||
@@ -40,7 +40,7 @@ logsource:
|
||||
detection:
|
||||
selection:
|
||||
EventID: 11
|
||||
TargetFileName:
|
||||
TargetFilename:
|
||||
- '*\AppData\Roaming\Oracle\bin\java*.exe'
|
||||
- '*\Retrive*.vbs'
|
||||
---
|
||||
|
||||
@@ -12,7 +12,7 @@ detection:
|
||||
- '*icacls * /grant Everyone:F /T /C /Q*'
|
||||
- '*bcdedit /set {default} recoveryenabled no*'
|
||||
- '*wbadmin delete catalog -quiet*'
|
||||
condition: selection1 or selection2
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: critical
|
||||
|
||||
@@ -4,6 +4,7 @@ description: Detects PsExec service installation and execution events (service a
|
||||
author: Thomas Patzke
|
||||
references:
|
||||
- https://www.jpcert.or.jp/english/pub/sr/ir_research.html
|
||||
- https://jpcertcc.github.io/ToolAnalysisResultSheet
|
||||
logsource:
|
||||
product: windows
|
||||
detection:
|
||||
@@ -18,7 +19,7 @@ detection:
|
||||
EventID: 1
|
||||
Image: '*\PSEXESVC.exe'
|
||||
User: 'NT AUTHORITY\SYSTEM'
|
||||
condition: service_installation or service_execution or sysmon_processcreation
|
||||
condition: 1 of them
|
||||
fields:
|
||||
- EventID
|
||||
- CommandLine
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
title: WMI Persistence
|
||||
status: experimental
|
||||
description: Detects suspicious WMI event filter and command line event consumer based on event id 5861 (Windows 10, 2012 and higher)
|
||||
description: Detects suspicious WMI event filter and command line event consumer based on event id 5861 and 5859 (Windows 10, 2012 and higher)
|
||||
author: Florian Roth
|
||||
references:
|
||||
- https://twitter.com/mattifestation/status/899646620148539397
|
||||
- https://www.eideon.com/2018-03-02-THL03-WMIBackdoors/
|
||||
logsource:
|
||||
product: windows
|
||||
service: wmi
|
||||
@@ -11,10 +12,13 @@ detection:
|
||||
selection:
|
||||
EventID: 5861
|
||||
keywords:
|
||||
- 'ActiveScriptEventConsumer'
|
||||
- 'CommandLineEventConsumer'
|
||||
- 'CommandLineTemplate'
|
||||
- 'Binding EventFilter'
|
||||
condition: selection and 1 of keywords
|
||||
selection2:
|
||||
EventID: 5859
|
||||
condition: selection and 1 of keywords or selection2
|
||||
falsepositives:
|
||||
- Unknown (data set is too small; further testing needed)
|
||||
level: high
|
||||
|
||||
@@ -14,7 +14,7 @@ detection:
|
||||
EventID: 4104
|
||||
keyword:
|
||||
- 'PromptForCredential'
|
||||
condition: selection and keyword
|
||||
condition: all of them
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: high
|
||||
|
||||
@@ -11,9 +11,9 @@ logsource:
|
||||
detection:
|
||||
selection:
|
||||
EventID: 4103
|
||||
keywords:
|
||||
keyword:
|
||||
- 'PS ATTACK!!!'
|
||||
condition: selection and keywords
|
||||
condition: all of them
|
||||
falsepositives:
|
||||
- Pentesters
|
||||
level: high
|
||||
|
||||
@@ -16,8 +16,9 @@ detection:
|
||||
noninteractive:
|
||||
- ' -noni '
|
||||
- ' -noninteractive '
|
||||
condition: encoded and hidden and noninteractive
|
||||
condition: all of them
|
||||
falsepositives:
|
||||
- Penetration tests
|
||||
- Very special / sneaky PowerShell scripts
|
||||
level: high
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
title: SquiblyTwo
|
||||
status: experimental
|
||||
description: Detects WMI SquiblyTwo Attack with possible renamed WMI by looking for imphash
|
||||
references:
|
||||
- https://subt0x11.blogspot.ch/2018/04/wmicexe-whitelisting-bypass-hacking.html
|
||||
- https://twitter.com/mattifestation/status/986280382042595328
|
||||
author: Markus Neis / Florian Roth
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: medium
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection1:
|
||||
EventID: 1
|
||||
Image:
|
||||
- '*\wmic.exe'
|
||||
CommandLine:
|
||||
- 'wmic * *format:\"http*'
|
||||
- "wmic * /format:'http"
|
||||
- 'wmic * /format:http*'
|
||||
selection2:
|
||||
EventID: 1
|
||||
Imphash:
|
||||
- '1B1A3F43BF37B5BFE60751F2EE2F326E'
|
||||
- '37777A96245A3C74EB217308F3546F4C'
|
||||
- '9D87C9D67CE724033C0B40CC4CA1B206'
|
||||
CommandLine:
|
||||
- '* *format:\"http*'
|
||||
- "* /format:'http"
|
||||
- '* /format:http*'
|
||||
condition: 1 of them
|
||||
@@ -0,0 +1,23 @@
|
||||
title: cmdkey Cached Credentials Recon
|
||||
status: experimental
|
||||
description: Detects usage of cmdkey to look for cached credentials.
|
||||
reference:
|
||||
- https://www.peew.pw/blog/2017/11/26/exploring-cmdkey-an-edge-case-for-privilege-escalation
|
||||
- https://technet.microsoft.com/en-us/library/cc754243(v=ws.11).aspx
|
||||
author: jmallette
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
Image: '*\cmdkey.exe'
|
||||
CommandLine: '* /list *'
|
||||
condition: selection
|
||||
fields:
|
||||
- CommandLine
|
||||
- ParentCommandLine
|
||||
- User
|
||||
falsepositives:
|
||||
- Legitimate administrative tasks.
|
||||
level: low
|
||||
@@ -15,7 +15,7 @@ detection:
|
||||
dnsregmod:
|
||||
EventID: 13
|
||||
TargetObject: '*\services\DNS\Parameters\ServerLevelPluginDll'
|
||||
condition: dnsadmin or dnsregmod
|
||||
condition: 1 of them
|
||||
fields:
|
||||
- EventID
|
||||
- CommandLine
|
||||
|
||||
@@ -19,7 +19,7 @@ detection:
|
||||
combination2:
|
||||
SourceImage: '*\Microsoft Office\*'
|
||||
CallTrace: '*|UNKNOWN*'
|
||||
condition: selection and ( combination1 or combination2 )
|
||||
condition: selection and 1 of combination*
|
||||
falsepositives:
|
||||
- unknown
|
||||
level: high
|
||||
|
||||
@@ -22,7 +22,7 @@ detection:
|
||||
- '*\regsvr32.exe'
|
||||
- '*\BITSADMIN*'
|
||||
filter:
|
||||
Commandline:
|
||||
CommandLine:
|
||||
- '*/HP/HP*'
|
||||
- '*\HP\HP*'
|
||||
condition: selection and not filter
|
||||
|
||||
@@ -3,7 +3,9 @@ status: experimental
|
||||
description: Detects a Windows command line executable started from Microsoft Word, Excel, Powerpoint, Publisher and Visio.
|
||||
references:
|
||||
- https://www.hybrid-analysis.com/sample/465aabe132ccb949e75b8ab9c5bda36d80cf2fd503d52b8bad54e295f28bbc21?environmentId=100
|
||||
author: Michael Haag
|
||||
- https://mgreen27.github.io/posts/2018/04/02/DownloadCradle.html
|
||||
author: Michael Haag, Florian Roth
|
||||
date: 2018/04/06
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
@@ -16,6 +18,7 @@ detection:
|
||||
- '*\POWERPNT.exe'
|
||||
- '*\MSPUB.exe'
|
||||
- '*\VISIO.exe'
|
||||
- '*\OUTLOOK.EXE'
|
||||
Image:
|
||||
- '*\cmd.exe'
|
||||
- '*\powershell.exe'
|
||||
@@ -27,6 +30,10 @@ detection:
|
||||
- '*\schtasks.exe' # see https://www.hybrid-analysis.com/sample/b409538c99f99b94a5035d9fa44a506b41be0feb23e89b7e4d272ba791aa6002?environmentId=100
|
||||
- '*\regsvr32.exe' # see https://twitter.com/subTee/status/899283365647458305
|
||||
- '*\hh.exe' # see https://www.hybrid-analysis.com/sample/6abc2b63f1865a847ff7f5a9d49bb944397b36f5503b9718d6f91f93d60f7cd7?environmentId=100
|
||||
- '*\wmic.exe' # see https://mgreen27.github.io/posts/2018/04/02/DownloadCradle.html
|
||||
- '*\mshta.exe' # see https://mgreen27.github.io/posts/2018/04/02/DownloadCradle.html
|
||||
- '*\rundll32.exe' # see https://mgreen27.github.io/posts/2018/04/02/DownloadCradle.html
|
||||
- '*\msiexec.exe' # see https://twitter.com/DissectMalware/status/984252467474026497
|
||||
condition: selection
|
||||
fields:
|
||||
- CommandLine
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
title: Microsoft Outlook Spawning Windows Shell
|
||||
status: experimental
|
||||
description: Detects a Windows command line executable started from Microsoft Outlook
|
||||
references:
|
||||
- https://www2.cybereason.com/asset/60:research-cobalt-kitty-attack-lifecycle
|
||||
author: Florian Roth
|
||||
date: 2018/03/06
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
ParentImage:
|
||||
- '*\OUTLOOK.EXE'
|
||||
Image:
|
||||
- '*\cmd.exe'
|
||||
- '*\powershell.exe'
|
||||
- '*\wscript.exe'
|
||||
- '*\cscript.exe'
|
||||
- '*\sh.exe'
|
||||
- '*\bash.exe'
|
||||
- '*\schtasks.exe'
|
||||
condition: selection
|
||||
fields:
|
||||
- CommandLine
|
||||
- ParentCommandLine
|
||||
falsepositives:
|
||||
- False positives are possible, depends on organisation and processes
|
||||
level: high
|
||||
|
||||
@@ -7,7 +7,7 @@ logsource:
|
||||
detection:
|
||||
selection:
|
||||
EventID: 8
|
||||
TargetProcess: 'C:\Windows\System32\lsass.exe'
|
||||
TargetImage: 'C:\Windows\System32\lsass.exe'
|
||||
StartModule: null
|
||||
condition: selection
|
||||
falsepositives:
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
title: Suspicious PowerShell Parameter Combination
|
||||
status: experimental
|
||||
description: Detects suspicious PowerShell invocation command parameters
|
||||
author: Florian Roth
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
keywords:
|
||||
- 'powershell'
|
||||
encoded:
|
||||
- ' -enc '
|
||||
- ' -EncodedCommand '
|
||||
hidden:
|
||||
- ' -w hidden '
|
||||
- ' -window hidden '
|
||||
- ' -windowstyle hidden '
|
||||
noninteractive:
|
||||
- ' -noni '
|
||||
- ' -noninteractive '
|
||||
condition: keywords and encoded and hidden and noninteractive
|
||||
falsepositives:
|
||||
- Penetration tests
|
||||
- Very special / sneaky PowerShell scripts
|
||||
level: high
|
||||
@@ -51,7 +51,7 @@ detection:
|
||||
- ' -encod '
|
||||
- ' -enco '
|
||||
- ' -en '
|
||||
condition: keywords and substrings
|
||||
condition: all of them
|
||||
falsepositives:
|
||||
- Penetration tests
|
||||
level: high
|
||||
|
||||
@@ -13,7 +13,7 @@ detection:
|
||||
selection:
|
||||
# Sysmon: File Creation (ID 11)
|
||||
EventID: 11
|
||||
TargetFileName: '*\AppData\Local\Temp\SAM-*.dmp*'
|
||||
TargetFilename: '*\AppData\Local\Temp\SAM-*.dmp*'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- Unknown
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
title: Windows Shell Spawning Suspicious Program
|
||||
status: experimental
|
||||
description: Detects a suspicious child process of a Windows shell
|
||||
references:
|
||||
- https://mgreen27.github.io/posts/2018/04/02/DownloadCradle.html
|
||||
author: Florian Roth
|
||||
date: 20018/04/06
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
ParentImage:
|
||||
- '*\mshta.exe'
|
||||
- '*\powershell.exe'
|
||||
- '*\cmd.exe'
|
||||
- '*\rundll32.exe'
|
||||
- '*\cscript.exe'
|
||||
- '*\wscript.exe'
|
||||
- '*\wmiprvse.exe'
|
||||
Image:
|
||||
- '*\schtasks.exe'
|
||||
- '*\nslookup.exe'
|
||||
- '*\certutil.exe'
|
||||
- '*\bitsadmin.exe'
|
||||
- '*\mshta.exe'
|
||||
condition: selection
|
||||
fields:
|
||||
- CommandLine
|
||||
- ParentCommandLine
|
||||
falsepositives:
|
||||
- Administrative scripts
|
||||
level: medium
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
title: Sticky Key Like Backdoor Usage
|
||||
description: Detects the usage and installation of a backdoor that uses an option to register a malicious debugger for built-in tools that are accessible in the login screen
|
||||
references:
|
||||
- https://blogs.technet.microsoft.com/jonathantrull/2016/10/03/detecting-sticky-key-backdoors/
|
||||
author: Florian Roth, @twjackomo
|
||||
date: 2018/03/15
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection_process:
|
||||
EventID: 1
|
||||
ParentImage:
|
||||
- '*\winlogon.exe'
|
||||
CommandLine:
|
||||
- '*\cmd.exe sethc.exe *'
|
||||
- '*\cmd.exe utilman.exe *'
|
||||
- '*\cmd.exe osk.exe *'
|
||||
- '*\cmd.exe Magnify.exe *'
|
||||
- '*\cmd.exe Narrator.exe *'
|
||||
- '*\cmd.exe DisplaySwitch.exe *'
|
||||
selection_registry:
|
||||
EventID: 13
|
||||
TargetObject:
|
||||
- '*\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\sethc.exe\Debugger'
|
||||
- '*\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\utilman.exe\Debugger'
|
||||
- '*\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\osk.exe\Debugger'
|
||||
- '*\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\Magnify.exe\Debugger'
|
||||
- '*\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\Narrator.exe\Debugger'
|
||||
- '*\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\DisplaySwitch.exe\Debugger'
|
||||
EventType: 'SetValue'
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- Unlikely
|
||||
level: critical
|
||||
|
||||
@@ -2,8 +2,7 @@ title: Suspicious Certutil Command
|
||||
status: experimental
|
||||
description: Detetcs a suspicious Microsoft certutil execution with sub commands like 'decode' sub command, which is sometimes used to decode malicious code with the built-in certutil utility
|
||||
author:
|
||||
- Florian Roth
|
||||
- juju4
|
||||
- Florian Roth, juju4
|
||||
references:
|
||||
- https://twitter.com/JohnLaTwC/status/835149808817991680
|
||||
- https://twitter.com/subTee/status/888102593838362624
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
title: Ping Hex IP
|
||||
description: Detects a ping command that uses a hex encoded IP address
|
||||
references:
|
||||
- https://github.com/vysec/Aggressor-VYSEC/blob/master/ping.cna
|
||||
- https://twitter.com/vysecurity/status/977198418354491392
|
||||
author: Florian Roth
|
||||
date: 2018/03/23
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
CommandLine:
|
||||
- '*\ping.exe 0x*'
|
||||
- '*\ping 0x*'
|
||||
condition: selection
|
||||
fields:
|
||||
- ParentCommandLine
|
||||
falsepositives:
|
||||
- Unlikely, because no sane admin pings IP addresses in a hexadecimal form
|
||||
level: high
|
||||
|
||||
@@ -22,7 +22,7 @@ detection:
|
||||
selection3:
|
||||
EventID: 1
|
||||
Image: '*\regsvr32.exe'
|
||||
Commandline:
|
||||
CommandLine:
|
||||
- '*/i:http* scrobj.dll'
|
||||
- '*/i:ftp* scrobj.dll'
|
||||
# Regsvr32.exe spawned wscript.exe process - indicator of COM scriptlet
|
||||
@@ -31,7 +31,12 @@ detection:
|
||||
EventID: 1
|
||||
Image: '*\wscript.exe'
|
||||
ParentImage: '*\regsvr32.exe'
|
||||
condition: selection1 or selection2 or selection3 or selection4
|
||||
# https://twitter.com/danielhbohannon/status/974321840385531904
|
||||
selection5:
|
||||
EventID: 1
|
||||
Image: '*\EXCEL.EXE'
|
||||
CommandLine: '*..\..\..\Windows\System32\regsvr32.exe *'
|
||||
condition: 1 of them
|
||||
fields:
|
||||
- CommandLine
|
||||
- ParentCommandLine
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
title: Taskmgr as LOCAL_SYSTEM
|
||||
status: experimental
|
||||
description: Detects the creation of taskmgr.exe process in context of LOCAL_SYSTEM
|
||||
author: Florian Roth
|
||||
date: 2018/03/18
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
User: 'NT AUTHORITY\SYSTEM'
|
||||
Image: '*\taskmgr.exe'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- Unkown
|
||||
level: high
|
||||
@@ -0,0 +1,24 @@
|
||||
title: Taskmgr as Parent
|
||||
status: experimental
|
||||
description: Detects the creation of a process from Windows task manager
|
||||
author: Florian Roth
|
||||
date: 2018/03/13
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
ParentImage: '*\taskmgr.exe'
|
||||
filter:
|
||||
Image:
|
||||
- 'resmon.exe'
|
||||
- 'mmc.exe'
|
||||
condition: selection and not filter
|
||||
fields:
|
||||
- Image
|
||||
- CommandLine
|
||||
- ParentCommandLine
|
||||
falsepositives:
|
||||
- Administrative activity
|
||||
level: low
|
||||
@@ -0,0 +1,20 @@
|
||||
title: Suspicious TSCON Start
|
||||
status: experimental
|
||||
description: Detects a tscon.exe start as LOCAL SYSTEM
|
||||
reference:
|
||||
- http://www.korznikov.com/2017/03/0-day-or-feature-privilege-escalation.html
|
||||
- https://medium.com/@networksecurity/rdp-hijacking-how-to-hijack-rds-and-remoteapp-sessions-transparently-to-move-through-an-da2a1e73a5f6
|
||||
author: Florian Roth
|
||||
date: 2018/03/17
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
User: 'NT AUTHORITY\SYSTEM'
|
||||
Image: '*\tscon.exe'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: high
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
action: global
|
||||
title: Suspicious RDP Redirect Using TSCON
|
||||
status: experimental
|
||||
description: Detects a suspicious RDP session redirect using tscon.exe
|
||||
reference:
|
||||
- http://www.korznikov.com/2017/03/0-day-or-feature-privilege-escalation.html
|
||||
- https://medium.com/@networksecurity/rdp-hijacking-how-to-hijack-rds-and-remoteapp-sessions-transparently-to-move-through-an-da2a1e73a5f6
|
||||
author: Florian Roth
|
||||
date: 2018/03/17
|
||||
detection:
|
||||
selection:
|
||||
CommandLine: '* /dest:rdp-tcp:*'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- Unknown
|
||||
level: high
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 1
|
||||
---
|
||||
logsource:
|
||||
product: windows
|
||||
service: security
|
||||
description: 'Requirements: Audit Policy : Detailed Tracking > Audit Process creation, Group Policy : Administrative Templates\System\Audit Process Creation'
|
||||
detection:
|
||||
selection:
|
||||
EventID: 4688
|
||||
@@ -0,0 +1,21 @@
|
||||
title: Registry Persistence Mechanisms
|
||||
description: Detects persistence registry keys
|
||||
references:
|
||||
- https://oddvar.moe/2018/04/10/persistence-using-globalflags-in-image-file-execution-options-hidden-from-autoruns-exe/
|
||||
date: 2018/04/11
|
||||
author: Karneades
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection_reg1:
|
||||
EventID: 13
|
||||
TargetObject:
|
||||
- '*\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\*\GlobalFlag'
|
||||
- '*\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\*\ReportingMode'
|
||||
- '*\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\*\MonitorProcess'
|
||||
EventType: 'SetValue'
|
||||
condition: 1 of them
|
||||
falsepositives:
|
||||
- unknown
|
||||
level: critical
|
||||
@@ -0,0 +1,19 @@
|
||||
title: WMI Persistence - Command Line Event Consumer
|
||||
status: experimental
|
||||
description: Detects WMI command line event consumers
|
||||
references:
|
||||
- https://www.eideon.com/2018-03-02-THL03-WMIBackdoors/
|
||||
author: Thomas Patzke
|
||||
date: 2018/03/07
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 7
|
||||
Image: 'C:\Windows\System32\wbem\WmiPrvSE.exe'
|
||||
ImageLoaded: 'wbemcons.dll'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- Unknown (data set is too small; further testing needed)
|
||||
level: high
|
||||
@@ -0,0 +1,18 @@
|
||||
title: WMI Persistence - Script Event Consumer File Write
|
||||
status: experimental
|
||||
description: Detects file writes of WMI script event consumer
|
||||
references:
|
||||
- https://www.eideon.com/2018-03-02-THL03-WMIBackdoors/
|
||||
author: Thomas Patzke
|
||||
date: 2018/03/07
|
||||
logsource:
|
||||
product: windows
|
||||
service: sysmon
|
||||
detection:
|
||||
selection:
|
||||
EventID: 11
|
||||
Image: 'C:\WINDOWS\system32\wbem\scrcons.exe'
|
||||
condition: selection
|
||||
falsepositives:
|
||||
- Unknown (data set is too small; further testing needed)
|
||||
level: high
|
||||
@@ -0,0 +1 @@
|
||||
{ "query": { "query_string": { "query": $query } } }
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
# CI Test script: generate all queries with es-qs backend and test them against local ES instance.
|
||||
# Copyright 2018 Thomas Patzke
|
||||
|
||||
# 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 asyncio
|
||||
import functools
|
||||
import sys
|
||||
import pprint
|
||||
import elasticsearch
|
||||
import elasticsearch_async
|
||||
pp = pprint.PrettyPrinter()
|
||||
|
||||
# Configuration
|
||||
eshost = "localhost:9200"
|
||||
index = "test"
|
||||
sigmac_cmd = "tools/sigmac"
|
||||
sigmac_processing_prefix = "* Processing Sigma input "
|
||||
|
||||
es = elasticsearch.Elasticsearch(hosts=[eshost])
|
||||
esa = elasticsearch_async.AsyncElasticsearch(hosts=[eshost])
|
||||
|
||||
# Create empty test index
|
||||
try:
|
||||
es.indices.create(index)
|
||||
except elasticsearch.exceptions.RequestError as e:
|
||||
if e.error != 'resource_already_exists_exception': # accept already existing index with same name
|
||||
raise e
|
||||
|
||||
queries = asyncio.Queue()
|
||||
|
||||
# sigmac runner coroutinne
|
||||
async def run_sigmac():
|
||||
sigmac = asyncio.create_subprocess_exec(
|
||||
sigmac_cmd, "-t", "es-qs", "-v", "-I", "-r", "rules/",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
)
|
||||
print("* Launching sigmac")
|
||||
proc = await sigmac
|
||||
print("* sigmac launched with PID {}".format(proc.pid))
|
||||
|
||||
cur_rule = None
|
||||
while True:
|
||||
line = await proc.stdout.readline()
|
||||
if not line:
|
||||
print("* sigmac finished")
|
||||
await queries.put((None, None))
|
||||
break
|
||||
else:
|
||||
strline = str(line, 'utf-8').rstrip()
|
||||
if strline.startswith(sigmac_processing_prefix):
|
||||
cur_rule = strline[len(sigmac_processing_prefix):]
|
||||
else:
|
||||
await queries.put((cur_rule, strline))
|
||||
await proc.wait()
|
||||
|
||||
exitcode = proc.returncode
|
||||
print("* sigmac returned with exit code {}".format(exitcode))
|
||||
return exitcode
|
||||
|
||||
# Generated query checker loop
|
||||
async def check_queries():
|
||||
failed = list()
|
||||
print("# Waiting for queries")
|
||||
while True:
|
||||
rule, query = await queries.get()
|
||||
if query is not None:
|
||||
print("# Checking query (rule {}): {}".format(rule, query))
|
||||
result = await esa.indices.validate_query(index=index, q=query)
|
||||
valid = result['valid']
|
||||
|
||||
print("# Received Result for rule {} query={}: {}".format(rule, query, valid))
|
||||
if not valid:
|
||||
try:
|
||||
detail_result = await esa.search(index=index, q=query)
|
||||
except Exception as e:
|
||||
error = e.info
|
||||
|
||||
failed.append((rule, query, error))
|
||||
queries.task_done()
|
||||
else:
|
||||
queries.task_done()
|
||||
break
|
||||
print("# Finished query checks")
|
||||
|
||||
return failed
|
||||
|
||||
task_check_query = asyncio.ensure_future(check_queries())
|
||||
task_sigmac = asyncio.ensure_future(run_sigmac())
|
||||
tasks = [
|
||||
task_check_query,
|
||||
task_sigmac
|
||||
]
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
done, pending = loop.run_until_complete(asyncio.wait(tasks))
|
||||
loop.close()
|
||||
esa.transport.close()
|
||||
print()
|
||||
|
||||
# Check if sigmac runned successfully
|
||||
try:
|
||||
if task_sigmac.result() != 0: # sigmac failed
|
||||
print("!!! sigmac failed while test!")
|
||||
sys.exit(1)
|
||||
except Exception:
|
||||
print("!!! sigmac failed while test!")
|
||||
sys.exit(2)
|
||||
|
||||
# Check if query checks failed
|
||||
try:
|
||||
query_check_result = task_check_query.result()
|
||||
except Exception:
|
||||
print("!!! Query check failed!")
|
||||
sys.exit(3)
|
||||
|
||||
query_check_result_cnt = len(query_check_result)
|
||||
if query_check_result_cnt > 0:
|
||||
print("!!! {} queries failed to check:".format(query_check_result_cnt))
|
||||
for rule, query, error in query_check_result:
|
||||
print("- {}: {}".format(rule, query))
|
||||
print("Error:")
|
||||
pp.pprint(error)
|
||||
print()
|
||||
sys.exit(4)
|
||||
else:
|
||||
print("All query checks passed!")
|
||||
@@ -0,0 +1,93 @@
|
||||
logsources:
|
||||
windows-application:
|
||||
product: windows
|
||||
service: application
|
||||
index: logs-endpoint-winevent-application-*
|
||||
windows-security:
|
||||
product: windows
|
||||
service: security
|
||||
index: logs-endpoint-winevent-security-*
|
||||
windows-sysmon:
|
||||
product: windows
|
||||
service: sysmon
|
||||
index: logs-endpoint-winevent-sysmon-*
|
||||
windows-system:
|
||||
product: windows
|
||||
service: system
|
||||
index: logs-endpoint-winevent-system-*
|
||||
windows-wmi:
|
||||
product: windows
|
||||
service: wmi
|
||||
index: logs-endpoint-winevent-wmiactivity-*
|
||||
windows-powershell:
|
||||
product: windows
|
||||
service: powershell
|
||||
index: logs-endpoint-winevent-powershell-*
|
||||
windows-powershell-classic:
|
||||
product: windows
|
||||
service: powershell-classic
|
||||
index: logs-endpoint-winevent-powershell-*
|
||||
defaultindex: logs-*
|
||||
fieldmappings:
|
||||
AccessMask: object_access_mask_requested
|
||||
AccountName: service_account_name
|
||||
AllowedToDelegateTo: user_attribute_allowed_todelegate
|
||||
AttributeLDAPDisplayName: dsobject_attribute_name
|
||||
AuditPolicyChanges: policy_changes
|
||||
AuthenticationPackageName: logon_authentication_package
|
||||
CallTrace: process_calltrace
|
||||
CommandLine: command_line
|
||||
ComputerName: host_name
|
||||
CurrentDirectory: process_current_directory
|
||||
DestinationHostname: dst_host
|
||||
DestinationIp: dst_ip
|
||||
DestinationIsIpv6: dst_isipv6
|
||||
DestinationPort: dst_port_number
|
||||
Details: registry_details
|
||||
EngineVersion: powershell.engine.version
|
||||
EventID: event_id
|
||||
EventType:
|
||||
EventID=12: registry_event_type
|
||||
EventID=13: registry_event_type
|
||||
EventID=14: registry_event_type
|
||||
EventID=19: wmi_event_type
|
||||
EventID=20: wmi_event_type
|
||||
EventID=21: wmi_event_type
|
||||
FailureCode: ticket_failure_code
|
||||
GrantedAccess: process_granted_access
|
||||
GroupName: group_name
|
||||
HiveName: hive_name
|
||||
HostVersion: powershell.host.version
|
||||
Image: process_path
|
||||
ImageLoaded: image_loaded
|
||||
LogonProcessName: logon_process_name
|
||||
LogonType: logon_type
|
||||
NewProcessName: process_path
|
||||
ObjectClass: dsobject_class
|
||||
ObjectName: object_name
|
||||
ObjectType: object_type
|
||||
ObjectValueName: object_value_name
|
||||
OperationType: object_operation_type
|
||||
ParentImage: process_parent_path
|
||||
PipeName: pipe_name
|
||||
ProcessName: process_path
|
||||
RelativeTargetName: share_relative_target_name
|
||||
ServiceFileName: service_image_path
|
||||
ServiceName: service_name
|
||||
ShareName: share_name
|
||||
Source: source_name
|
||||
SourceImage: process_path
|
||||
StartModule: thread_startmodule
|
||||
Status: logon_failure_status
|
||||
SubjectUserName: user_name
|
||||
TargetFilename: file_name
|
||||
TargetImage: process_target_path
|
||||
TargetObject: registry_target_object
|
||||
TargetImage: target_process_path
|
||||
TaskName: task_name
|
||||
TicketEncryptionType: ticket_encryption_type
|
||||
TicketOptions: ticket_options
|
||||
User: user
|
||||
UserName: user_name
|
||||
Workstation: src_host
|
||||
WorkstationName: src_host
|
||||
@@ -24,7 +24,7 @@ fieldmappings:
|
||||
FailureCode: result_code
|
||||
GroupName: group_name
|
||||
KeyLength: key_length
|
||||
LogonProcess: logon_process
|
||||
LogonProcessName: logon_process
|
||||
LogonType: logon_type
|
||||
ServiceName: service
|
||||
SubjectAccountName:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
coverage>=4.4.1
|
||||
PyYAML>=3.11
|
||||
yamllint>=1.10.0
|
||||
elasticsearch
|
||||
elasticsearch-async
|
||||
setuptools
|
||||
wheel
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ with open(path.join(here, 'README.md'), encoding='utf-8') as f:
|
||||
|
||||
setup(
|
||||
name='sigmatools',
|
||||
version='0.2',
|
||||
version='0.4',
|
||||
description='Tools for the Generic Signature Format for SIEM Systems',
|
||||
long_description=long_description,
|
||||
url='https://github.com/Neo23x0/sigma',
|
||||
|
||||
+243
-29
@@ -78,6 +78,7 @@ class BaseBackend:
|
||||
index_field = None # field name that is used to address indices
|
||||
output_class = None # one of the above output classes
|
||||
file_list = None
|
||||
options = tuple() # a list of tuples with following elements: option name, default value, help text, target attribute name (option name if None)
|
||||
|
||||
def __init__(self, sigmaconfig, backend_options=None, filename=None):
|
||||
"""
|
||||
@@ -87,18 +88,26 @@ class BaseBackend:
|
||||
super().__init__()
|
||||
if not isinstance(sigmaconfig, (sigma.config.SigmaConfiguration, None)):
|
||||
raise TypeError("SigmaConfiguration object expected")
|
||||
self.options = backend_options
|
||||
self.backend_options = backend_options
|
||||
self.sigmaconfig = sigmaconfig
|
||||
self.sigmaconfig.set_backend(self)
|
||||
self.output = self.output_class(filename)
|
||||
|
||||
# Parse options
|
||||
for option, default_value, _, target in self.options:
|
||||
if target is None:
|
||||
target = option
|
||||
setattr(self, target, self.backend_options.setdefault(option, default_value))
|
||||
|
||||
def generate(self, sigmaparser):
|
||||
"""Method is called for each sigma rule and receives the parsed rule (SigmaParser)"""
|
||||
for parsed in sigmaparser.condparsed:
|
||||
before = self.generateBefore(parsed)
|
||||
if before is not None:
|
||||
self.output.print(before, end="")
|
||||
self.output.print(self.generateQuery(parsed))
|
||||
query = self.generateQuery(parsed)
|
||||
if query is not None:
|
||||
self.output.print(query)
|
||||
after = self.generateAfter(parsed)
|
||||
if after is not None:
|
||||
self.output.print(after, end="")
|
||||
@@ -193,14 +202,163 @@ class QuoteCharMixin:
|
||||
class RulenameCommentMixin:
|
||||
"""Prefixes each rule with the rule title."""
|
||||
prefix = "# "
|
||||
options = (
|
||||
("rulecomment", False, "Prefix generated query with comment containing title", None),
|
||||
)
|
||||
|
||||
def generateBefore(self, parsed):
|
||||
if "rulecomment" in self.options:
|
||||
if self.rulecomment:
|
||||
try:
|
||||
return "\n%s%s\n" % (self.prefix, parsed.sigmaParser.parsedyaml['title'])
|
||||
except KeyError:
|
||||
return ""
|
||||
|
||||
class ElasticsearchDSLBackend(RulenameCommentMixin, BaseBackend):
|
||||
"""ElasticSearch DSL backend"""
|
||||
identifier = 'es-dsl'
|
||||
active = True
|
||||
output_class = SingleOutput
|
||||
options = (
|
||||
("es", "http://localhost:9200", "Host and port of Elasticsearch instance", None),
|
||||
("output", "import", "Output format: import = JSON search request, curl = Shell script that do the search queries via curl", "output_type"),
|
||||
)
|
||||
interval = None
|
||||
title = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.queries = []
|
||||
|
||||
def generate(self, sigmaparser):
|
||||
"""Method is called for each sigma rule and receives the parsed rule (SigmaParser)"""
|
||||
self.title = sigmaparser.parsedyaml["title"]
|
||||
self.indices = sigmaparser.get_logsource().index
|
||||
if len(self.indices) == 0:
|
||||
self.indices = None
|
||||
|
||||
try:
|
||||
self.interval = sigmaparser.parsedyaml['detection']['timeframe']
|
||||
except:
|
||||
pass
|
||||
|
||||
for parsed in sigmaparser.condparsed:
|
||||
self.generateBefore(parsed)
|
||||
self.generateQuery(parsed)
|
||||
self.generateAfter(parsed)
|
||||
|
||||
def generateQuery(self, parsed):
|
||||
self.queries[-1]['query']['constant_score']['filter'] = self.generateNode(parsed.parsedSearch)
|
||||
if parsed.parsedAgg:
|
||||
self.generateAggregation(parsed.parsedAgg)
|
||||
# if parsed.parsedAgg:
|
||||
# fields += self.generateAggregation(parsed.parsedAgg)
|
||||
# self.fields.update(fields)
|
||||
|
||||
def generateANDNode(self, node):
|
||||
andNode = {'bool': {'must': []}}
|
||||
for val in node:
|
||||
andNode['bool']['must'].append(self.generateNode(val))
|
||||
return andNode
|
||||
|
||||
def generateORNode(self, node):
|
||||
orNode = {'bool': {'should': []}}
|
||||
for val in node:
|
||||
orNode['bool']['should'].append(self.generateNode(val))
|
||||
return orNode
|
||||
|
||||
def generateNOTNode(self, node):
|
||||
notNode = {'bool': {'must_not': []}}
|
||||
for val in node:
|
||||
notNode['bool']['must_not'].append(self.generateNode(val))
|
||||
return notNode
|
||||
|
||||
def generateSubexpressionNode(self, node):
|
||||
return self.generateNode(node.items)
|
||||
|
||||
def generateListNode(self, node):
|
||||
raise NotImplementedError("%s : (%s) Node type not implemented for this backend"%(self.title, 'generateListNode'))
|
||||
|
||||
def generateMapItemNode(self, node):
|
||||
key, value = node
|
||||
if type(value) not in (str, int, list):
|
||||
raise TypeError("Map values must be strings, numbers or lists, not " + str(type(value)))
|
||||
if type(value) is list:
|
||||
res = {'bool': {'should': []}}
|
||||
for v in value:
|
||||
res['bool']['should'].append({'match': {key: v}})
|
||||
return res
|
||||
else:
|
||||
return {'match': {key: value}}
|
||||
|
||||
def generateValueNode(self, node):
|
||||
return {'multi_match': {'query': node, 'fields': []}}
|
||||
|
||||
def generateNULLValueNode(self, node):
|
||||
return {'missing': {'field': node.item}}
|
||||
|
||||
def generateNotNULLValueNode(self, node):
|
||||
return {'exists': {'field': node.item}}
|
||||
|
||||
def generateAggregation(self, agg):
|
||||
if agg:
|
||||
if agg.aggfunc == sigma.parser.SigmaAggregationParser.AGGFUNC_COUNT:
|
||||
if agg.groupfield is not None:
|
||||
self.queries[-1]['aggs'] = {
|
||||
'%s_count'%agg.groupfield: {
|
||||
'terms': {
|
||||
'field': '%s'%agg.groupfield
|
||||
},
|
||||
'aggs': {
|
||||
'limit': {
|
||||
'bucket_selector': {
|
||||
'buckets_path': {
|
||||
'count': '_count'
|
||||
},
|
||||
'script': 'params.count %s %s'%(agg.cond_op, agg.condition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else:
|
||||
for name, idx in agg.aggfuncmap.items():
|
||||
if idx == agg.aggfunc:
|
||||
funcname = name
|
||||
break
|
||||
raise NotImplementedError("%s : The '%s' aggregation operator is not yet implemented for this backend"%(self.title, funcname))
|
||||
|
||||
|
||||
def generateBefore(self, parsed):
|
||||
self.queries.append({'query': {'constant_score': {'filter': {}}}})
|
||||
|
||||
def generateAfter(self, parsed):
|
||||
dateField = 'date'
|
||||
if self.sigmaconfig.config and 'dateField' in self.sigmaconfig.config:
|
||||
dateField = self.sigmaconfig.config['dateField']
|
||||
if self.interval:
|
||||
if 'bool' not in self.queries[-1]['query']['constant_score']['filter']:
|
||||
self.queries[-1]['query']['constant_score']['filter'] = {'bool': {'must': []}}
|
||||
if 'must' not in self.queries[-1]['query']['constant_score']['filter']['bool']:
|
||||
self.queries[-1]['query']['constant_score']['filter']['bool']['must'] = []
|
||||
|
||||
self.queries[-1]['query']['constant_score']['filter']['bool']['must'].append({'range': {dateField: {'gte': 'now-%s'%self.interval}}})
|
||||
|
||||
def finalize(self):
|
||||
"""
|
||||
Is called after the last file was processed with generate(). The right place if this backend is not intended to
|
||||
look isolated at each rule, but generates an output which incorporates multiple rules, e.g. dashboards.
|
||||
"""
|
||||
index = ''
|
||||
if self.indices is not None and len(self.indices) == 1:
|
||||
index = '%s/'%self.indices[0]
|
||||
|
||||
for query in self.queries:
|
||||
if self.output_type == 'curl':
|
||||
self.output.print("\curl -XGET '%s/%s_search?pretty' -H 'Content-Type: application/json' -d'"%(self.es, index))
|
||||
self.output.print(json.dumps(query, indent=2))
|
||||
if self.output_type == 'curl':
|
||||
self.output.print("'")
|
||||
|
||||
class SingleTextQueryBackend(RulenameCommentMixin, BaseBackend, QuoteCharMixin):
|
||||
"""Base class for backends that generate one text-based expression from a Sigma rule"""
|
||||
identifier = "base-textquery"
|
||||
@@ -273,9 +431,9 @@ class MultiRuleOutputMixin:
|
||||
* Unique name by addition of a counter if generated name already in usage
|
||||
|
||||
Generated names are tracked by the Mixin.
|
||||
|
||||
|
||||
"""
|
||||
rulename = sigmaparser.parsedyaml["title"].replace(" ", "-")
|
||||
rulename = sigmaparser.parsedyaml["title"].replace(" ", "-").replace("(", "").replace(")", "")
|
||||
if rulename in self.rulenames: # add counter if name collides
|
||||
cnt = 2
|
||||
while "%s-%d" % (rulename, cnt) in self.rulenames:
|
||||
@@ -292,7 +450,7 @@ class ElasticsearchQuerystringBackend(SingleTextQueryBackend):
|
||||
identifier = "es-qs"
|
||||
active = True
|
||||
|
||||
reEscape = re.compile("([+\\-=!(){}\\[\\]^\"~:\\\\/]|&&|\\|\\|)")
|
||||
reEscape = re.compile("([+\\-=!(){}\\[\\]^\"~:/]|\\\\(?![*?])|\\\\u|&&|\\|\\|)")
|
||||
reClear = re.compile("[<>]")
|
||||
andToken = " AND "
|
||||
orToken = " OR "
|
||||
@@ -311,10 +469,17 @@ class KibanaBackend(ElasticsearchQuerystringBackend, MultiRuleOutputMixin):
|
||||
identifier = "kibana"
|
||||
active = True
|
||||
output_class = SingleOutput
|
||||
options = (
|
||||
("output", "import", "Output format: import = JSON file manually imported in Kibana, curl = Shell script that imports queries in Kibana via curl (jq is additionally required)", "output_type"),
|
||||
("es", "localhost:9200", "Host and port of Elasticsearch instance", None),
|
||||
("index", ".kibana", "Kibana index", None),
|
||||
("prefix", "Sigma: ", "Title prefix of Sigma queries", None),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.kibanaconf = list()
|
||||
self.indexsearch = set()
|
||||
|
||||
def generate(self, sigmaparser):
|
||||
rulename = self.getRuleName(sigmaparser)
|
||||
@@ -345,12 +510,16 @@ class KibanaBackend(ElasticsearchQuerystringBackend, MultiRuleOutputMixin):
|
||||
if len(indices) > 1: # add index names if rule must be replicated because of ambigiuous index patterns
|
||||
raise NotSupportedError("Multiple target indices are not supported by Kibana")
|
||||
else:
|
||||
title = sigmaparser.parsedyaml["title"]
|
||||
try:
|
||||
title = self.options["prefix"] + title
|
||||
except KeyError:
|
||||
pass
|
||||
title = self.prefix + sigmaparser.parsedyaml["title"]
|
||||
|
||||
self.indexsearch.add(
|
||||
"export {indexvar}=$(curl -s '{es}/{index}/_search?q=index-pattern.title:{indexpattern}' | jq -r '.hits.hits[0]._id | ltrimstr(\"index-pattern:\")')".format(
|
||||
es=self.es,
|
||||
index=self.index,
|
||||
indexpattern=index.replace("*", "\\*"),
|
||||
indexvar=self.index_variable_name(index)
|
||||
)
|
||||
)
|
||||
self.kibanaconf.append({
|
||||
"_id": final_rulename,
|
||||
"_type": "search",
|
||||
@@ -362,7 +531,7 @@ class KibanaBackend(ElasticsearchQuerystringBackend, MultiRuleOutputMixin):
|
||||
"sort": ["@timestamp", "desc"],
|
||||
"version": 1,
|
||||
"kibanaSavedObjectMeta": {
|
||||
"searchSourceJSON": json.dumps({
|
||||
"searchSourceJSON": {
|
||||
"index": index,
|
||||
"filter": [],
|
||||
"highlight": {
|
||||
@@ -379,32 +548,53 @@ class KibanaBackend(ElasticsearchQuerystringBackend, MultiRuleOutputMixin):
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
def finalize(self):
|
||||
self.output.print(json.dumps(self.kibanaconf, indent=2))
|
||||
if self.output_type == "import": # output format that can be imported via Kibana UI
|
||||
for item in self.kibanaconf: # JSONize kibanaSavedObjectMeta.searchSourceJSON
|
||||
item['_source']['kibanaSavedObjectMeta']['searchSourceJSON'] = json.dumps(item['_source']['kibanaSavedObjectMeta']['searchSourceJSON'])
|
||||
self.output.print(json.dumps(self.kibanaconf, indent=2))
|
||||
elif self.output_type == "curl":
|
||||
for item in self.indexsearch:
|
||||
self.output.print(item)
|
||||
for item in self.kibanaconf:
|
||||
item['_source']['kibanaSavedObjectMeta']['searchSourceJSON']['index'] = "$" + self.index_variable_name(item['_source']['kibanaSavedObjectMeta']['searchSourceJSON']['index']) # replace index pattern with reference to variable that will contain Kibana index UUID at script runtime
|
||||
item['_source']['kibanaSavedObjectMeta']['searchSourceJSON'] = json.dumps(item['_source']['kibanaSavedObjectMeta']['searchSourceJSON']) # Convert it to JSON string as expected by Kibana
|
||||
item['_source']['kibanaSavedObjectMeta']['searchSourceJSON'] = item['_source']['kibanaSavedObjectMeta']['searchSourceJSON'].replace("\\", "\\\\") # Add further escaping for escaped quotes for shell
|
||||
self.output.print(
|
||||
"curl -s -XPUT -H 'Content-Type: application/json' --data-binary @- '{es}/{index}/doc/{doc_id}' <<EOF\n{doc}\nEOF".format(
|
||||
es=self.es,
|
||||
index=self.index,
|
||||
doc_id="search:" + item['_id'],
|
||||
doc=json.dumps({
|
||||
"type": "search",
|
||||
"search": item['_source']
|
||||
}, indent=2)
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError("Output type '%s' not supported" % self.output_type)
|
||||
|
||||
def index_variable_name(self, index):
|
||||
return "index_" + index.replace("-", "__").replace("*", "X")
|
||||
|
||||
class XPackWatcherBackend(ElasticsearchQuerystringBackend, MultiRuleOutputMixin):
|
||||
"""Converts Sigma Rule into X-Pack Watcher JSON for alerting"""
|
||||
identifier = "xpack-watcher"
|
||||
active = True
|
||||
output_class = SingleOutput
|
||||
options = (
|
||||
("output", "curl", "Output format: curl = Shell script that imports queries in Watcher index with curl", "output_type"),
|
||||
("es", "localhost:9200", "Host and port of Elasticsearch instance", None),
|
||||
("mail", None, "Mail address for Watcher notification (only logging if not set)", None),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.watcher_alert = dict()
|
||||
try:
|
||||
self.output_type = self.options["output"]
|
||||
except KeyError:
|
||||
self.output_type = "curl"
|
||||
|
||||
try:
|
||||
self.es = self.options["es"]
|
||||
except KeyError:
|
||||
self.es = "localhost:9200"
|
||||
|
||||
def generate(self, sigmaparser):
|
||||
# get the details if this alert occurs
|
||||
@@ -512,7 +702,7 @@ class XPackWatcherBackend(ElasticsearchQuerystringBackend, MultiRuleOutputMixin)
|
||||
# Building the action
|
||||
action_subject = "Sigma Rule '%s'" % title
|
||||
try: # mail notification if mail address is given
|
||||
email = self.options['mail']
|
||||
email = self.mail
|
||||
action = {
|
||||
"send_email": {
|
||||
"email": {
|
||||
@@ -583,7 +773,7 @@ class LogPointBackend(SingleTextQueryBackend):
|
||||
identifier = "logpoint"
|
||||
active = True
|
||||
|
||||
reEscape = re.compile('(["\\\\])')
|
||||
reEscape = re.compile('("|\\\\(?![*?]))')
|
||||
reClear = None
|
||||
andToken = " "
|
||||
orToken = " OR "
|
||||
@@ -597,7 +787,7 @@ class LogPointBackend(SingleTextQueryBackend):
|
||||
mapExpression = "%s=%s"
|
||||
mapListsSpecialHandling = True
|
||||
mapListValueExpression = "%s IN %s"
|
||||
|
||||
|
||||
def generateAggregation(self, agg):
|
||||
if agg == None:
|
||||
return ""
|
||||
@@ -607,14 +797,14 @@ class LogPointBackend(SingleTextQueryBackend):
|
||||
return " | chart %s(%s) as val | search val %s %s" % (agg.aggfunc_notrans, agg.aggfield, agg.cond_op, agg.condition)
|
||||
else:
|
||||
return " | chart %s(%s) as val by %s | search val %s %s" % (agg.aggfunc_notrans, agg.aggfield, agg.groupfield, agg.cond_op, agg.condition)
|
||||
|
||||
|
||||
class SplunkBackend(SingleTextQueryBackend):
|
||||
"""Converts Sigma rule into Splunk Search Processing Language (SPL)."""
|
||||
identifier = "splunk"
|
||||
active = True
|
||||
index_field = "index"
|
||||
|
||||
reEscape = re.compile('(["\\\\])')
|
||||
reEscape = re.compile('("|\\\\(?![*?]))')
|
||||
reClear = None
|
||||
andToken = " "
|
||||
orToken = " OR "
|
||||
@@ -689,8 +879,15 @@ class FieldnameListBackend(BaseBackend):
|
||||
active = True
|
||||
output_class = SingleOutput
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields = set()
|
||||
|
||||
def generateQuery(self, parsed):
|
||||
return "\n".join(sorted(set(list(flatten(self.generateNode(parsed.parsedSearch))))))
|
||||
fields = list(flatten(self.generateNode(parsed.parsedSearch)))
|
||||
if parsed.parsedAgg:
|
||||
fields += self.generateAggregation(parsed.parsedAgg)
|
||||
self.fields.update(fields)
|
||||
|
||||
def generateANDNode(self, node):
|
||||
return [self.generateNode(val) for val in node]
|
||||
@@ -718,6 +915,23 @@ class FieldnameListBackend(BaseBackend):
|
||||
def generateValueNode(self, node):
|
||||
return []
|
||||
|
||||
def generateNULLValueNode(self, node):
|
||||
return [node.item]
|
||||
|
||||
def generateNotNULLValueNode(self, node):
|
||||
return [node.item]
|
||||
|
||||
def generateAggregation(self, agg):
|
||||
fields = list()
|
||||
if agg.groupfield is not None:
|
||||
fields.append(agg.groupfield)
|
||||
if agg.aggfield is not None:
|
||||
fields.append(agg.aggfield)
|
||||
return fields
|
||||
|
||||
def finalize(self):
|
||||
self.output.print("\n".join(sorted(self.fields)))
|
||||
|
||||
# Helpers
|
||||
def flatten(l):
|
||||
for i in l:
|
||||
|
||||
+31
-7
@@ -101,8 +101,9 @@ class SigmaParser:
|
||||
def parse_sigma(self):
|
||||
try: # definition uniqueness check
|
||||
for definitionName, definition in self.parsedyaml["detection"].items():
|
||||
self.definitions[definitionName] = definition
|
||||
self.extract_values(definition) # builds key-values-table in self.values
|
||||
if definitionName != "condition":
|
||||
self.definitions[definitionName] = definition
|
||||
self.extract_values(definition) # builds key-values-table in self.values
|
||||
except KeyError:
|
||||
raise SigmaParseError("No detection definitions found")
|
||||
|
||||
@@ -283,7 +284,7 @@ class SigmaConditionTokenizer:
|
||||
(SigmaConditionToken.TOKEN_AND, re.compile("and", re.IGNORECASE)),
|
||||
(SigmaConditionToken.TOKEN_OR, re.compile("or", re.IGNORECASE)),
|
||||
(SigmaConditionToken.TOKEN_NOT, re.compile("not", re.IGNORECASE)),
|
||||
(SigmaConditionToken.TOKEN_ID, re.compile("\\w+")),
|
||||
(SigmaConditionToken.TOKEN_ID, re.compile("[\\w*]+")),
|
||||
(SigmaConditionToken.TOKEN_LPAR, re.compile("\\(")),
|
||||
(SigmaConditionToken.TOKEN_RPAR, re.compile("\\)")),
|
||||
]
|
||||
@@ -417,13 +418,36 @@ class NodeSubexpression(ParseTreeNode):
|
||||
self.items = subexpr
|
||||
|
||||
# Parse tree converters: convert something into one of the parse tree node classes defined above
|
||||
def convertXOf(sigma, val, condclass):
|
||||
"""
|
||||
Generic implementation of (1|all) of x expressions.
|
||||
|
||||
* condclass across all list items if x is name of definition
|
||||
* condclass across all definitions if x is keyword 'them'
|
||||
* condclass across all matching definition if x is wildcard expression, e.g. 'selection*'
|
||||
"""
|
||||
if val.matched == "them": # OR across all definitions
|
||||
cond = condclass()
|
||||
for definition in sigma.definitions.values():
|
||||
cond.add(NodeSubexpression(sigma.parse_definition(definition)))
|
||||
return NodeSubexpression(cond)
|
||||
elif val.matched.find("*") > 0: # OR across all matching definitions
|
||||
cond = condclass()
|
||||
reDefPat = re.compile("^" + val.matched.replace("*", ".*") + "$")
|
||||
for name, definition in sigma.definitions.items():
|
||||
if reDefPat.match(name):
|
||||
cond.add(NodeSubexpression(sigma.parse_definition(definition)))
|
||||
return NodeSubexpression(cond)
|
||||
else: # OR across all items of definition
|
||||
return NodeSubexpression(sigma.parse_definition_byname(val.matched, condclass))
|
||||
|
||||
def convertAllOf(sigma, op, val):
|
||||
"""Convert 'all of x' into ConditionAND"""
|
||||
return NodeSubexpression(sigma.parse_definition_byname(val.matched, ConditionAND))
|
||||
"""Convert 'all of x' expressions into ConditionAND"""
|
||||
return convertXOf(sigma, val, ConditionAND)
|
||||
|
||||
def convertOneOf(sigma, op, val):
|
||||
"""Convert '1 of x' into ConditionOR"""
|
||||
return NodeSubexpression(sigma.parse_definition_byname(val.matched, ConditionOR))
|
||||
"""Convert '1 of x' expressions into ConditionOR"""
|
||||
return convertXOf(sigma, val, ConditionOR)
|
||||
|
||||
def convertId(sigma, op):
|
||||
"""Convert search identifiers (lists or maps) into condition nodes according to spec defaults"""
|
||||
|
||||
+13
-1
@@ -54,7 +54,19 @@ def get_inputs(paths, recursive):
|
||||
else:
|
||||
return [pathlib.Path(p) for p in paths]
|
||||
|
||||
argparser = argparse.ArgumentParser(description="Convert Sigma rules into SIEM signatures.")
|
||||
class SigmacArgumentParser(argparse.ArgumentParser):
|
||||
def format_help(self):
|
||||
helptext = super().format_help() + "\nBackend options:\n"
|
||||
|
||||
for backend in backends.getBackendList():
|
||||
if len(backend.options) > 0:
|
||||
helptext += " " + backend.identifier + "\n"
|
||||
for option, default, help, _ in backend.options:
|
||||
helptext += " {:10}: {} (default: {})".format(option, help, default) + "\n"
|
||||
|
||||
return helptext
|
||||
|
||||
argparser = SigmacArgumentParser(description="Convert Sigma rules into SIEM signatures.")
|
||||
argparser.add_argument("--recurse", "-r", action="store_true", help="Recurse into subdirectories (not yet implemented)")
|
||||
argparser.add_argument("--filter", "-f", help="""
|
||||
Define comma-separated filters that must match (AND-linked) to rule to be processed.
|
||||
|
||||
Reference in New Issue
Block a user