sigma2sql 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sigma2sql-0.2.0/.gitignore +8 -0
- sigma2sql-0.2.0/CHANGELOG.md +29 -0
- sigma2sql-0.2.0/LICENSE +21 -0
- sigma2sql-0.2.0/PKG-INFO +277 -0
- sigma2sql-0.2.0/README.md +247 -0
- sigma2sql-0.2.0/examples/events.jsonl +12 -0
- sigma2sql-0.2.0/examples/rules/brute_force.match.jsonl +3 -0
- sigma2sql-0.2.0/examples/rules/brute_force.nomatch.jsonl +5 -0
- sigma2sql-0.2.0/examples/rules/brute_force.yml +26 -0
- sigma2sql-0.2.0/examples/rules/encoded_powershell.match.jsonl +3 -0
- sigma2sql-0.2.0/examples/rules/encoded_powershell.nomatch.jsonl +3 -0
- sigma2sql-0.2.0/examples/rules/encoded_powershell.yml +21 -0
- sigma2sql-0.2.0/examples/rules/office_spawns_shell.match.jsonl +2 -0
- sigma2sql-0.2.0/examples/rules/office_spawns_shell.nomatch.jsonl +2 -0
- sigma2sql-0.2.0/examples/rules/office_spawns_shell.yml +22 -0
- sigma2sql-0.2.0/examples/rules/rdp_from_outside.match.jsonl +2 -0
- sigma2sql-0.2.0/examples/rules/rdp_from_outside.nomatch.jsonl +3 -0
- sigma2sql-0.2.0/examples/rules/rdp_from_outside.yml +19 -0
- sigma2sql-0.2.0/pyproject.toml +78 -0
- sigma2sql-0.2.0/scripts/check_regression.py +107 -0
- sigma2sql-0.2.0/scripts/check_sigmahq.py +76 -0
- sigma2sql-0.2.0/scripts/demo_svg.py +40 -0
- sigma2sql-0.2.0/scripts/diff_pysigma.py +145 -0
- sigma2sql-0.2.0/src/sigma2sql/__init__.py +21 -0
- sigma2sql-0.2.0/src/sigma2sql/__main__.py +3 -0
- sigma2sql-0.2.0/src/sigma2sql/cli.py +320 -0
- sigma2sql-0.2.0/src/sigma2sql/collection.py +62 -0
- sigma2sql-0.2.0/src/sigma2sql/condition.py +119 -0
- sigma2sql-0.2.0/src/sigma2sql/correlation.py +82 -0
- sigma2sql-0.2.0/src/sigma2sql/detection.py +230 -0
- sigma2sql-0.2.0/src/sigma2sql/errors.py +6 -0
- sigma2sql-0.2.0/src/sigma2sql/expr.py +140 -0
- sigma2sql-0.2.0/src/sigma2sql/hunt.py +116 -0
- sigma2sql-0.2.0/src/sigma2sql/profile.py +119 -0
- sigma2sql-0.2.0/src/sigma2sql/profiles/__init__.py +0 -0
- sigma2sql-0.2.0/src/sigma2sql/profiles/ecs-winlogbeat.yml +43 -0
- sigma2sql-0.2.0/src/sigma2sql/profiles/evtx.yml +71 -0
- sigma2sql-0.2.0/src/sigma2sql/profiles/ocsf.yml +26 -0
- sigma2sql-0.2.0/src/sigma2sql/profiles/sysmon.yml +26 -0
- sigma2sql-0.2.0/src/sigma2sql/py.typed +0 -0
- sigma2sql-0.2.0/src/sigma2sql/rule.py +260 -0
- sigma2sql-0.2.0/src/sigma2sql/runner.py +82 -0
- sigma2sql-0.2.0/src/sigma2sql/sql.py +325 -0
- sigma2sql-0.2.0/tests/cases.py +228 -0
- sigma2sql-0.2.0/tests/test_check.py +54 -0
- sigma2sql-0.2.0/tests/test_cli.py +74 -0
- sigma2sql-0.2.0/tests/test_correlation.py +281 -0
- sigma2sql-0.2.0/tests/test_duckdb.py +25 -0
- sigma2sql-0.2.0/tests/test_hunt.py +72 -0
- sigma2sql-0.2.0/tests/test_parse.py +109 -0
- sigma2sql-0.2.0/tests/test_postgres.py +44 -0
- sigma2sql-0.2.0/tests/test_profile.py +193 -0
- sigma2sql-0.2.0/tests/test_sqlite.py +22 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 (2026-11-05)
|
|
4
|
+
|
|
5
|
+
- `hunt`: run a rule directory over JSON, JSON Lines, Parquet or CSV logs with DuckDB, no
|
|
6
|
+
database needed; table or JSON report, `--fail-on LEVEL` for CI.
|
|
7
|
+
- DuckDB dialect next to SQLite and PostgreSQL.
|
|
8
|
+
- Correlation rules (`event_count`, `value_count`, `value_sum`, `value_avg`, `temporal`, with
|
|
9
|
+
`group-by`, `timespan` and `aliases`) and legacy `| count() by ... > N` aggregations. All 45
|
|
10
|
+
`count()` rules SigmaHQ keeps under `unsupported/` and `deprecated/` convert and run.
|
|
11
|
+
- Profiles: field mapping, logsource routing (per-category table and/or extra conditions),
|
|
12
|
+
nested JSON paths, fallback field templates and timestamp settings. Built in: `sysmon`,
|
|
13
|
+
`evtx`, `ecs-winlogbeat`, `ocsf`.
|
|
14
|
+
- Modifiers `base64`, `base64offset`, `utf16`, `utf16le`, `utf16be`, `wide`, and keyword
|
|
15
|
+
searches with modifiers (`'|all'`, `'|re'`). SigmaHQ coverage is now 3162/3162 on SQLite and
|
|
16
|
+
DuckDB, 3160/3162 on PostgreSQL.
|
|
17
|
+
- `check` command and a GitHub Action that test rules against
|
|
18
|
+
`<rule>.match.jsonl` / `<rule>.nomatch.jsonl` files.
|
|
19
|
+
- Correctness checks in CI: SigmaHQ regression samples (476/476 match as expected) and a
|
|
20
|
+
rule-by-rule comparison with pySigma's SQLite backend on real events (see
|
|
21
|
+
`docs/correctness.md`).
|
|
22
|
+
- Fix: `windash` now also expands values written with `/` (e.g. ` /accepteula`), and every
|
|
23
|
+
flag in a value.
|
|
24
|
+
- `--version`.
|
|
25
|
+
|
|
26
|
+
## 0.1.0 (2026-11-05)
|
|
27
|
+
|
|
28
|
+
- First release: Sigma rules to SQLite and PostgreSQL SQL, field mapping, JSON column mode,
|
|
29
|
+
and `test` to run a rule against JSON Lines events.
|
sigma2sql-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmet Burak Özer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sigma2sql-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sigma2sql
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Sigma rules as SQL for SQLite, PostgreSQL and DuckDB: convert, hunt through log files and test rules
|
|
5
|
+
Project-URL: Homepage, https://github.com/brkozer/sigma2sql
|
|
6
|
+
Project-URL: Issues, https://github.com/brkozer/sigma2sql/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/brkozer/sigma2sql/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Ahmet Burak Özer
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: detection-engineering,duckdb,postgresql,siem,sigma,sql,threat-hunting
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Database
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
26
|
+
Provides-Extra: duckdb
|
|
27
|
+
Requires-Dist: duckdb>=1.1; extra == 'duckdb'
|
|
28
|
+
Requires-Dist: numpy>=2.0; extra == 'duckdb'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# sigma2sql
|
|
32
|
+
|
|
33
|
+
[](https://github.com/brkozer/sigma2sql/actions/workflows/ci.yml)
|
|
34
|
+

|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
Run [Sigma](https://sigmahq.io) detection rules as SQL on **SQLite**, **PostgreSQL** and
|
|
38
|
+
**DuckDB**: hunt through log files without a SIEM, convert rules for the database your logs
|
|
39
|
+
already live in, and unit-test rules before they reach production.
|
|
40
|
+
|
|
41
|
+
- **Every SigmaHQ rule converts**: 3162 / 3162 on SQLite and DuckDB, 3160 / 3162 on
|
|
42
|
+
PostgreSQL, plus all 45 `count()` aggregation rules SigmaHQ keeps under `unsupported/`.
|
|
43
|
+
Every generated query is executed on all three databases in CI each week.
|
|
44
|
+
- **Checked against real events**: all 476 of SigmaHQ's regression samples match as expected,
|
|
45
|
+
and on those events sigma2sql agrees with pySigma on 3074 of 3076 rules; the two
|
|
46
|
+
differences are IPv6 addresses pySigma's text matching misses
|
|
47
|
+
([details](docs/correctness.md)).
|
|
48
|
+
- **Correlations**: event counts, distinct counts, sums and temporal sequences over a sliding
|
|
49
|
+
window, as plain SQL.
|
|
50
|
+
- **Your schema**: profiles map Sigma fields to your columns or nested JSON (Sysmon, EVTX
|
|
51
|
+
exports, Elastic/Winlogbeat ECS, OCSF built in) and limit each rule to the right log source.
|
|
52
|
+
|
|
53
|
+
<p align="center"><img src="https://raw.githubusercontent.com/brkozer/sigma2sql/main/docs/demo.svg" alt="sigma2sql hunting through sample logs, checking rule tests and converting a rule to PostgreSQL" width="100%"></p>
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv tool install "sigma2sql[duckdb] @ git+https://github.com/brkozer/sigma2sql"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The `duckdb` extra is needed for `hunt`; `convert`, `test` and `check` only need PyYAML.
|
|
62
|
+
|
|
63
|
+
## Hunt through log files
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone --depth 1 https://github.com/SigmaHQ/sigma
|
|
67
|
+
sigma2sql hunt sigma/rules --logs 'logs/*.json' --profile evtx --examples 1
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`hunt` loads JSON, JSON Lines, Parquet or CSV files into an in-memory DuckDB database, runs
|
|
71
|
+
every rule and correlation, and lists the ones that matched, most severe first. On the
|
|
72
|
+
examples in this repository:
|
|
73
|
+
|
|
74
|
+
```console
|
|
75
|
+
$ sigma2sql hunt examples/rules --logs examples/events.jsonl
|
|
76
|
+
LEVEL HITS RULE
|
|
77
|
+
high 2 PowerShell started with an encoded command (examples/rules/encoded_powershell.yml)
|
|
78
|
+
high 1 Brute force against one account (examples/rules/brute_force.yml)
|
|
79
|
+
high 1 Office application starting a shell (examples/rules/office_spawns_shell.yml)
|
|
80
|
+
medium 1 Successful RDP logon from a public address (examples/rules/rdp_from_outside.yml)
|
|
81
|
+
4 of 4 rules matched 12 events (0 rules skipped)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`--examples N` prints matching events (or correlation results such as
|
|
85
|
+
`{"TargetUserName":"admin","window_start":"2026-11-01 03:12:01","window_end":"2026-11-01 03:12:17","value":3}`),
|
|
86
|
+
`--format json` gives a machine-readable report and `--fail-on high` a non-zero exit code
|
|
87
|
+
when a rule at that level or above fires.
|
|
88
|
+
|
|
89
|
+
## Convert rules
|
|
90
|
+
|
|
91
|
+
```console
|
|
92
|
+
$ sigma2sql convert examples/rules/office_spawns_shell.yml -d postgres --json-column data
|
|
93
|
+
-- Office application starting a shell (8a7f3e91-2c44-4b8e-b0f6-3e5d9c1a7b02)
|
|
94
|
+
SELECT * FROM "events" WHERE (("data"->>'ParentImage' ILIKE '%\\winword.exe' ESCAPE '\' OR ...)
|
|
95
|
+
AND ("data"->>'Image' ILIKE '%\\cmd.exe' ESCAPE '\' OR ...));
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A whole directory works the same way, e.g.
|
|
99
|
+
`sigma2sql convert sigma/rules -d postgres -t security.events --json-column event --profile ecs-winlogbeat`.
|
|
100
|
+
Without `--json-column` each Sigma field is a column. Unsupported rules are listed on stderr
|
|
101
|
+
with the reason, and the command exits non-zero.
|
|
102
|
+
|
|
103
|
+
| Option | Meaning |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `-d, --dialect` | `sqlite` (default), `postgres` or `duckdb` |
|
|
106
|
+
| `-t, --table` | table name, `schema.table` allowed (default `events`) |
|
|
107
|
+
| `--json-column` | read fields from this JSON / JSONB column |
|
|
108
|
+
| `--keyword-column` | column searched by keyword rules (in JSON mode the whole document) |
|
|
109
|
+
| `-p, --profile` | built-in profile or a YAML file (see below) |
|
|
110
|
+
| `-m, --mapping` | YAML map from Sigma field to your column or key, applied over the profile |
|
|
111
|
+
| `--nested` | dotted names are paths into nested JSON |
|
|
112
|
+
| `--timestamp-field`, `--timestamp-format` | event time for correlations (`iso`, `epoch_s`, `epoch_ms`) |
|
|
113
|
+
|
|
114
|
+
## Correlations
|
|
115
|
+
|
|
116
|
+
Sigma correlation rules and the older `| count() by ... > N` syntax both become SQL. With a
|
|
117
|
+
`timespan`, each event closes a window over the events of the same group before it; the
|
|
118
|
+
window's events are found with a self-join, which works the same on all three databases and
|
|
119
|
+
allows distinct counts:
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
title: Brute force against one account
|
|
123
|
+
correlation:
|
|
124
|
+
type: event_count # also value_count, value_sum, value_avg, temporal
|
|
125
|
+
rules: [failed_logon]
|
|
126
|
+
group-by: [TargetUserName]
|
|
127
|
+
timespan: 5m
|
|
128
|
+
condition: {gte: 3}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Each result row holds the group values, the window's start and end and the counted value.
|
|
132
|
+
Rules referenced by a correlation are not reported on their own unless it sets
|
|
133
|
+
`generate: true`. `temporal_ordered` is not supported yet.
|
|
134
|
+
|
|
135
|
+
## Profiles
|
|
136
|
+
|
|
137
|
+
A profile describes where your events keep Sigma's fields and which events each log source
|
|
138
|
+
covers. `sigma2sql profiles` lists the built-in ones:
|
|
139
|
+
|
|
140
|
+
| Profile | For |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `sysmon` | Sysmon events with their own field names; each category limited to its event ID |
|
|
143
|
+
| `evtx` | EVTX exported to JSON by evtx_dump or python-evtx (`Event.System`, `Event.EventData`) |
|
|
144
|
+
| `ecs-winlogbeat` | Windows events shipped by Winlogbeat in Elastic Common Schema |
|
|
145
|
+
| `ocsf` | OCSF 1.x events, time in epoch milliseconds |
|
|
146
|
+
|
|
147
|
+
Your own profile is a small YAML file:
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
nested: true
|
|
151
|
+
timestamp: "@timestamp"
|
|
152
|
+
default_field: "winlog.event_data.{field}" # fallback for unmapped fields
|
|
153
|
+
fields:
|
|
154
|
+
Image: process.executable
|
|
155
|
+
EventID: [event.code, winlog.event_id] # first one present wins
|
|
156
|
+
logsources:
|
|
157
|
+
- match: {product: windows, category: process_creation}
|
|
158
|
+
table: process_events # optional: route to a table
|
|
159
|
+
where: {event.code: "1"} # optional: extra condition
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Without a profile, rules are not limited by log source: a network rule can then match a
|
|
163
|
+
process event that happens to have the same field values. The built-in profiles cover the
|
|
164
|
+
common Windows sources; for other data, add `logsources` entries.
|
|
165
|
+
|
|
166
|
+
## Test rules
|
|
167
|
+
|
|
168
|
+
Put example events next to a rule, one JSON object per line:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
rules/office_spawns_shell.yml
|
|
172
|
+
rules/office_spawns_shell.match.jsonl # every event must match
|
|
173
|
+
rules/office_spawns_shell.nomatch.jsonl # no event may match
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```console
|
|
177
|
+
$ sigma2sql check examples/rules --require-tests
|
|
178
|
+
ok examples/rules/brute_force.yml
|
|
179
|
+
ok examples/rules/encoded_powershell.yml
|
|
180
|
+
ok examples/rules/office_spawns_shell.yml
|
|
181
|
+
ok examples/rules/rdp_from_outside.yml
|
|
182
|
+
4 passed, 0 failed, 0 without tests
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
A failure names the rule, the file and the line, e.g. after adding an Outlook-spawned
|
|
186
|
+
`cmd.exe` to the match file of a rule that only watches Word, Excel and PowerPoint:
|
|
187
|
+
|
|
188
|
+
```console
|
|
189
|
+
FAIL rules/office_spawns_shell.yml: Office application starting a shell: missed office_spawns_shell.match.jsonl event(s) 1
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The same check as a GitHub Action:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
- uses: brkozer/sigma2sql@v0.2.0
|
|
196
|
+
with:
|
|
197
|
+
path: rules
|
|
198
|
+
profile: sysmon # optional
|
|
199
|
+
require-tests: "true" # optional
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`sigma2sql test RULE EVENTS.jsonl` prints the events (or correlation results) a single rule
|
|
203
|
+
produces.
|
|
204
|
+
|
|
205
|
+
## Supported Sigma features
|
|
206
|
+
|
|
207
|
+
| Feature | SQLite | PostgreSQL | DuckDB |
|
|
208
|
+
| --- | --- | --- | --- |
|
|
209
|
+
| maps, lists, keywords (with `all`, `re` and string modifiers) | ✓ | ✓ | ✓ |
|
|
210
|
+
| `and`, `or`, `not`, `1 of x*`, `all of x*`, `them`, condition lists | ✓ | ✓ | ✓ |
|
|
211
|
+
| wildcards and escapes, `contains`, `startswith`, `endswith`, `all`, `cased`, `windash` | ✓ | ✓ | ✓ |
|
|
212
|
+
| `base64`, `base64offset`, `utf16`, `utf16le`, `utf16be`, `wide` | ✓ | ✓ | ✓ |
|
|
213
|
+
| `re`, `re\|i` | `REGEXP` ¹ | `~` ² | `regexp_matches` ³ |
|
|
214
|
+
| `cidr` (IPv4 and IPv6, any notation) | `cidr_match()` ¹ | `inet <<=` | `cidr_match()` ¹ |
|
|
215
|
+
| `lt`, `lte`, `gt`, `gte`, `exists`, `fieldref`, `null` | ✓ | ✓ | ✓ |
|
|
216
|
+
| correlations, `count()` aggregations | ✓ | ✓ | ✓ |
|
|
217
|
+
| `expand` (placeholders), `temporal_ordered`, filter rules | – | – | – |
|
|
218
|
+
|
|
219
|
+
¹ Not built into the database. `hunt`, `test` and `check` register them; when you run the SQL
|
|
220
|
+
elsewhere, register `regexp(pattern, value)` and `cidr_match(value, network)` yourself
|
|
221
|
+
(`sigma2sql.runner.connect()` returns a SQLite connection with both).<br>
|
|
222
|
+
² PostgreSQL rejects repetition counts above 255; those two rules are reported as unsupported
|
|
223
|
+
rather than silently changed.<br>
|
|
224
|
+
³ RE2: no lookaround or backreferences; such rules are reported as unsupported on DuckDB.
|
|
225
|
+
|
|
226
|
+
Matching is case-insensitive by default (ASCII in SQLite, as its `LIKE` and `NOCASE` are);
|
|
227
|
+
values in a JSON column match whether they are stored as `4624` or `"4624"`, and `not` on a
|
|
228
|
+
missing field is true, as Sigma intends.
|
|
229
|
+
|
|
230
|
+
## How it works
|
|
231
|
+
|
|
232
|
+
```mermaid
|
|
233
|
+
flowchart LR
|
|
234
|
+
Y[rule files] --> L[loader<br/>rules, correlations,<br/>count aggregations]
|
|
235
|
+
L --> C[condition parser]
|
|
236
|
+
L --> D[search compiler<br/>modifiers, wildcards,<br/>base64, windash]
|
|
237
|
+
C --> E[match tree]
|
|
238
|
+
D --> E
|
|
239
|
+
P[profile<br/>fields, logsources,<br/>timestamp] --> R
|
|
240
|
+
E --> R[renderer<br/>SQLite · PostgreSQL · DuckDB]
|
|
241
|
+
R --> Q[SQL]
|
|
242
|
+
R --> K[correlation SQL<br/>self-join windows]
|
|
243
|
+
Q --> H[hunt / test / check]
|
|
244
|
+
K --> H
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Library use
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from sigma2sql import Target, load_rule_file, to_query
|
|
251
|
+
from sigma2sql.profile import apply_profile, load_profile
|
|
252
|
+
|
|
253
|
+
rule = load_rule_file("rule.yml")
|
|
254
|
+
target = apply_profile(
|
|
255
|
+
Target("postgres", "logs.windows", json_column="event"), load_profile("evtx")
|
|
256
|
+
)
|
|
257
|
+
print(to_query(rule, target))
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Development
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
uv sync
|
|
264
|
+
uv run pytest # SQLite and DuckDB
|
|
265
|
+
SIGMA2SQL_PG_DSN=postgresql://postgres:test@localhost/postgres uv run pytest # + PostgreSQL
|
|
266
|
+
uv run python scripts/check_sigmahq.py sigma/rules --dsn ... [--profile evtx]
|
|
267
|
+
uv run python scripts/check_regression.py sigma
|
|
268
|
+
uv run --with pysigma --with pysigma-backend-sqlite python scripts/diff_pysigma.py sigma
|
|
269
|
+
uv run python scripts/demo_svg.py docs/demo.svg
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The behaviour tests in `tests/cases.py` and `tests/test_correlation.py` run unchanged on all
|
|
273
|
+
three databases. The example rules and events in `examples/` were written for this project.
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
MIT
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# sigma2sql
|
|
2
|
+
|
|
3
|
+
[](https://github.com/brkozer/sigma2sql/actions/workflows/ci.yml)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Run [Sigma](https://sigmahq.io) detection rules as SQL on **SQLite**, **PostgreSQL** and
|
|
8
|
+
**DuckDB**: hunt through log files without a SIEM, convert rules for the database your logs
|
|
9
|
+
already live in, and unit-test rules before they reach production.
|
|
10
|
+
|
|
11
|
+
- **Every SigmaHQ rule converts**: 3162 / 3162 on SQLite and DuckDB, 3160 / 3162 on
|
|
12
|
+
PostgreSQL, plus all 45 `count()` aggregation rules SigmaHQ keeps under `unsupported/`.
|
|
13
|
+
Every generated query is executed on all three databases in CI each week.
|
|
14
|
+
- **Checked against real events**: all 476 of SigmaHQ's regression samples match as expected,
|
|
15
|
+
and on those events sigma2sql agrees with pySigma on 3074 of 3076 rules; the two
|
|
16
|
+
differences are IPv6 addresses pySigma's text matching misses
|
|
17
|
+
([details](docs/correctness.md)).
|
|
18
|
+
- **Correlations**: event counts, distinct counts, sums and temporal sequences over a sliding
|
|
19
|
+
window, as plain SQL.
|
|
20
|
+
- **Your schema**: profiles map Sigma fields to your columns or nested JSON (Sysmon, EVTX
|
|
21
|
+
exports, Elastic/Winlogbeat ECS, OCSF built in) and limit each rule to the right log source.
|
|
22
|
+
|
|
23
|
+
<p align="center"><img src="https://raw.githubusercontent.com/brkozer/sigma2sql/main/docs/demo.svg" alt="sigma2sql hunting through sample logs, checking rule tests and converting a rule to PostgreSQL" width="100%"></p>
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv tool install "sigma2sql[duckdb] @ git+https://github.com/brkozer/sigma2sql"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The `duckdb` extra is needed for `hunt`; `convert`, `test` and `check` only need PyYAML.
|
|
32
|
+
|
|
33
|
+
## Hunt through log files
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone --depth 1 https://github.com/SigmaHQ/sigma
|
|
37
|
+
sigma2sql hunt sigma/rules --logs 'logs/*.json' --profile evtx --examples 1
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`hunt` loads JSON, JSON Lines, Parquet or CSV files into an in-memory DuckDB database, runs
|
|
41
|
+
every rule and correlation, and lists the ones that matched, most severe first. On the
|
|
42
|
+
examples in this repository:
|
|
43
|
+
|
|
44
|
+
```console
|
|
45
|
+
$ sigma2sql hunt examples/rules --logs examples/events.jsonl
|
|
46
|
+
LEVEL HITS RULE
|
|
47
|
+
high 2 PowerShell started with an encoded command (examples/rules/encoded_powershell.yml)
|
|
48
|
+
high 1 Brute force against one account (examples/rules/brute_force.yml)
|
|
49
|
+
high 1 Office application starting a shell (examples/rules/office_spawns_shell.yml)
|
|
50
|
+
medium 1 Successful RDP logon from a public address (examples/rules/rdp_from_outside.yml)
|
|
51
|
+
4 of 4 rules matched 12 events (0 rules skipped)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`--examples N` prints matching events (or correlation results such as
|
|
55
|
+
`{"TargetUserName":"admin","window_start":"2026-11-01 03:12:01","window_end":"2026-11-01 03:12:17","value":3}`),
|
|
56
|
+
`--format json` gives a machine-readable report and `--fail-on high` a non-zero exit code
|
|
57
|
+
when a rule at that level or above fires.
|
|
58
|
+
|
|
59
|
+
## Convert rules
|
|
60
|
+
|
|
61
|
+
```console
|
|
62
|
+
$ sigma2sql convert examples/rules/office_spawns_shell.yml -d postgres --json-column data
|
|
63
|
+
-- Office application starting a shell (8a7f3e91-2c44-4b8e-b0f6-3e5d9c1a7b02)
|
|
64
|
+
SELECT * FROM "events" WHERE (("data"->>'ParentImage' ILIKE '%\\winword.exe' ESCAPE '\' OR ...)
|
|
65
|
+
AND ("data"->>'Image' ILIKE '%\\cmd.exe' ESCAPE '\' OR ...));
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A whole directory works the same way, e.g.
|
|
69
|
+
`sigma2sql convert sigma/rules -d postgres -t security.events --json-column event --profile ecs-winlogbeat`.
|
|
70
|
+
Without `--json-column` each Sigma field is a column. Unsupported rules are listed on stderr
|
|
71
|
+
with the reason, and the command exits non-zero.
|
|
72
|
+
|
|
73
|
+
| Option | Meaning |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `-d, --dialect` | `sqlite` (default), `postgres` or `duckdb` |
|
|
76
|
+
| `-t, --table` | table name, `schema.table` allowed (default `events`) |
|
|
77
|
+
| `--json-column` | read fields from this JSON / JSONB column |
|
|
78
|
+
| `--keyword-column` | column searched by keyword rules (in JSON mode the whole document) |
|
|
79
|
+
| `-p, --profile` | built-in profile or a YAML file (see below) |
|
|
80
|
+
| `-m, --mapping` | YAML map from Sigma field to your column or key, applied over the profile |
|
|
81
|
+
| `--nested` | dotted names are paths into nested JSON |
|
|
82
|
+
| `--timestamp-field`, `--timestamp-format` | event time for correlations (`iso`, `epoch_s`, `epoch_ms`) |
|
|
83
|
+
|
|
84
|
+
## Correlations
|
|
85
|
+
|
|
86
|
+
Sigma correlation rules and the older `| count() by ... > N` syntax both become SQL. With a
|
|
87
|
+
`timespan`, each event closes a window over the events of the same group before it; the
|
|
88
|
+
window's events are found with a self-join, which works the same on all three databases and
|
|
89
|
+
allows distinct counts:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
title: Brute force against one account
|
|
93
|
+
correlation:
|
|
94
|
+
type: event_count # also value_count, value_sum, value_avg, temporal
|
|
95
|
+
rules: [failed_logon]
|
|
96
|
+
group-by: [TargetUserName]
|
|
97
|
+
timespan: 5m
|
|
98
|
+
condition: {gte: 3}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Each result row holds the group values, the window's start and end and the counted value.
|
|
102
|
+
Rules referenced by a correlation are not reported on their own unless it sets
|
|
103
|
+
`generate: true`. `temporal_ordered` is not supported yet.
|
|
104
|
+
|
|
105
|
+
## Profiles
|
|
106
|
+
|
|
107
|
+
A profile describes where your events keep Sigma's fields and which events each log source
|
|
108
|
+
covers. `sigma2sql profiles` lists the built-in ones:
|
|
109
|
+
|
|
110
|
+
| Profile | For |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| `sysmon` | Sysmon events with their own field names; each category limited to its event ID |
|
|
113
|
+
| `evtx` | EVTX exported to JSON by evtx_dump or python-evtx (`Event.System`, `Event.EventData`) |
|
|
114
|
+
| `ecs-winlogbeat` | Windows events shipped by Winlogbeat in Elastic Common Schema |
|
|
115
|
+
| `ocsf` | OCSF 1.x events, time in epoch milliseconds |
|
|
116
|
+
|
|
117
|
+
Your own profile is a small YAML file:
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
nested: true
|
|
121
|
+
timestamp: "@timestamp"
|
|
122
|
+
default_field: "winlog.event_data.{field}" # fallback for unmapped fields
|
|
123
|
+
fields:
|
|
124
|
+
Image: process.executable
|
|
125
|
+
EventID: [event.code, winlog.event_id] # first one present wins
|
|
126
|
+
logsources:
|
|
127
|
+
- match: {product: windows, category: process_creation}
|
|
128
|
+
table: process_events # optional: route to a table
|
|
129
|
+
where: {event.code: "1"} # optional: extra condition
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Without a profile, rules are not limited by log source: a network rule can then match a
|
|
133
|
+
process event that happens to have the same field values. The built-in profiles cover the
|
|
134
|
+
common Windows sources; for other data, add `logsources` entries.
|
|
135
|
+
|
|
136
|
+
## Test rules
|
|
137
|
+
|
|
138
|
+
Put example events next to a rule, one JSON object per line:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
rules/office_spawns_shell.yml
|
|
142
|
+
rules/office_spawns_shell.match.jsonl # every event must match
|
|
143
|
+
rules/office_spawns_shell.nomatch.jsonl # no event may match
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```console
|
|
147
|
+
$ sigma2sql check examples/rules --require-tests
|
|
148
|
+
ok examples/rules/brute_force.yml
|
|
149
|
+
ok examples/rules/encoded_powershell.yml
|
|
150
|
+
ok examples/rules/office_spawns_shell.yml
|
|
151
|
+
ok examples/rules/rdp_from_outside.yml
|
|
152
|
+
4 passed, 0 failed, 0 without tests
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A failure names the rule, the file and the line, e.g. after adding an Outlook-spawned
|
|
156
|
+
`cmd.exe` to the match file of a rule that only watches Word, Excel and PowerPoint:
|
|
157
|
+
|
|
158
|
+
```console
|
|
159
|
+
FAIL rules/office_spawns_shell.yml: Office application starting a shell: missed office_spawns_shell.match.jsonl event(s) 1
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The same check as a GitHub Action:
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
- uses: brkozer/sigma2sql@v0.2.0
|
|
166
|
+
with:
|
|
167
|
+
path: rules
|
|
168
|
+
profile: sysmon # optional
|
|
169
|
+
require-tests: "true" # optional
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`sigma2sql test RULE EVENTS.jsonl` prints the events (or correlation results) a single rule
|
|
173
|
+
produces.
|
|
174
|
+
|
|
175
|
+
## Supported Sigma features
|
|
176
|
+
|
|
177
|
+
| Feature | SQLite | PostgreSQL | DuckDB |
|
|
178
|
+
| --- | --- | --- | --- |
|
|
179
|
+
| maps, lists, keywords (with `all`, `re` and string modifiers) | ✓ | ✓ | ✓ |
|
|
180
|
+
| `and`, `or`, `not`, `1 of x*`, `all of x*`, `them`, condition lists | ✓ | ✓ | ✓ |
|
|
181
|
+
| wildcards and escapes, `contains`, `startswith`, `endswith`, `all`, `cased`, `windash` | ✓ | ✓ | ✓ |
|
|
182
|
+
| `base64`, `base64offset`, `utf16`, `utf16le`, `utf16be`, `wide` | ✓ | ✓ | ✓ |
|
|
183
|
+
| `re`, `re\|i` | `REGEXP` ¹ | `~` ² | `regexp_matches` ³ |
|
|
184
|
+
| `cidr` (IPv4 and IPv6, any notation) | `cidr_match()` ¹ | `inet <<=` | `cidr_match()` ¹ |
|
|
185
|
+
| `lt`, `lte`, `gt`, `gte`, `exists`, `fieldref`, `null` | ✓ | ✓ | ✓ |
|
|
186
|
+
| correlations, `count()` aggregations | ✓ | ✓ | ✓ |
|
|
187
|
+
| `expand` (placeholders), `temporal_ordered`, filter rules | – | – | – |
|
|
188
|
+
|
|
189
|
+
¹ Not built into the database. `hunt`, `test` and `check` register them; when you run the SQL
|
|
190
|
+
elsewhere, register `regexp(pattern, value)` and `cidr_match(value, network)` yourself
|
|
191
|
+
(`sigma2sql.runner.connect()` returns a SQLite connection with both).<br>
|
|
192
|
+
² PostgreSQL rejects repetition counts above 255; those two rules are reported as unsupported
|
|
193
|
+
rather than silently changed.<br>
|
|
194
|
+
³ RE2: no lookaround or backreferences; such rules are reported as unsupported on DuckDB.
|
|
195
|
+
|
|
196
|
+
Matching is case-insensitive by default (ASCII in SQLite, as its `LIKE` and `NOCASE` are);
|
|
197
|
+
values in a JSON column match whether they are stored as `4624` or `"4624"`, and `not` on a
|
|
198
|
+
missing field is true, as Sigma intends.
|
|
199
|
+
|
|
200
|
+
## How it works
|
|
201
|
+
|
|
202
|
+
```mermaid
|
|
203
|
+
flowchart LR
|
|
204
|
+
Y[rule files] --> L[loader<br/>rules, correlations,<br/>count aggregations]
|
|
205
|
+
L --> C[condition parser]
|
|
206
|
+
L --> D[search compiler<br/>modifiers, wildcards,<br/>base64, windash]
|
|
207
|
+
C --> E[match tree]
|
|
208
|
+
D --> E
|
|
209
|
+
P[profile<br/>fields, logsources,<br/>timestamp] --> R
|
|
210
|
+
E --> R[renderer<br/>SQLite · PostgreSQL · DuckDB]
|
|
211
|
+
R --> Q[SQL]
|
|
212
|
+
R --> K[correlation SQL<br/>self-join windows]
|
|
213
|
+
Q --> H[hunt / test / check]
|
|
214
|
+
K --> H
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Library use
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from sigma2sql import Target, load_rule_file, to_query
|
|
221
|
+
from sigma2sql.profile import apply_profile, load_profile
|
|
222
|
+
|
|
223
|
+
rule = load_rule_file("rule.yml")
|
|
224
|
+
target = apply_profile(
|
|
225
|
+
Target("postgres", "logs.windows", json_column="event"), load_profile("evtx")
|
|
226
|
+
)
|
|
227
|
+
print(to_query(rule, target))
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Development
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
uv sync
|
|
234
|
+
uv run pytest # SQLite and DuckDB
|
|
235
|
+
SIGMA2SQL_PG_DSN=postgresql://postgres:test@localhost/postgres uv run pytest # + PostgreSQL
|
|
236
|
+
uv run python scripts/check_sigmahq.py sigma/rules --dsn ... [--profile evtx]
|
|
237
|
+
uv run python scripts/check_regression.py sigma
|
|
238
|
+
uv run --with pysigma --with pysigma-backend-sqlite python scripts/diff_pysigma.py sigma
|
|
239
|
+
uv run python scripts/demo_svg.py docs/demo.svg
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The behaviour tests in `tests/cases.py` and `tests/test_correlation.py` run unchanged on all
|
|
243
|
+
three databases. The example rules and events in `examples/` were written for this project.
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{"EventID": 1, "Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "ParentImage": "C:\\Windows\\explorer.exe", "CommandLine": "powershell.exe -NoProfile -enc SQBFAFgA"}
|
|
2
|
+
{"EventID": 1, "Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "ParentImage": "C:\\Program Files\\Microsoft Configuration Manager\\bin\\ccmexec.exe", "CommandLine": "powershell.exe -enc SQBFAFgA"}
|
|
3
|
+
{"EventID": 1, "Image": "C:\\Program Files\\PowerShell\\7\\pwsh.exe", "ParentImage": "C:\\Windows\\explorer.exe", "CommandLine": "pwsh.exe /ec SQBFAFgA"}
|
|
4
|
+
{"EventID": 1, "Image": "C:\\Windows\\System32\\cmd.exe", "ParentImage": "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE", "CommandLine": "cmd.exe /c whoami"}
|
|
5
|
+
{"EventID": 1, "Image": "C:\\Windows\\System32\\notepad.exe", "ParentImage": "C:\\Windows\\explorer.exe", "CommandLine": "notepad.exe report.txt"}
|
|
6
|
+
{"EventID": 4624, "LogonType": 10, "IpAddress": "203.0.113.45", "TargetUserName": "admin"}
|
|
7
|
+
{"EventID": "4624", "LogonType": "10", "IpAddress": "192.168.1.20", "TargetUserName": "admin"}
|
|
8
|
+
{"EventID": 4624, "LogonType": 3, "IpAddress": "198.51.100.7", "TargetUserName": "svc_backup"}
|
|
9
|
+
{"EventID": 4625, "LogonType": 3, "IpAddress": "198.51.100.23", "TargetUserName": "admin", "timestamp": "2026-11-01T03:12:01Z"}
|
|
10
|
+
{"EventID": 4625, "LogonType": 3, "IpAddress": "198.51.100.23", "TargetUserName": "admin", "timestamp": "2026-11-01T03:12:09Z"}
|
|
11
|
+
{"EventID": 4625, "LogonType": 3, "IpAddress": "198.51.100.23", "TargetUserName": "admin", "timestamp": "2026-11-01T03:12:17Z"}
|
|
12
|
+
{"EventID": 4625, "LogonType": 3, "IpAddress": "198.51.100.40", "TargetUserName": "svc_backup", "timestamp": "2026-11-01T09:00:00Z"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{"EventID": 4625, "TargetUserName": "admin", "timestamp": "2026-11-01T03:12:01Z"}
|
|
2
|
+
{"EventID": 4625, "TargetUserName": "admin", "timestamp": "2026-11-01T03:15:00Z"}
|
|
3
|
+
{"EventID": 4625, "TargetUserName": "admin", "timestamp": "2026-11-01T03:18:00Z"}
|
|
4
|
+
{"EventID": 4625, "TargetUserName": "alice", "timestamp": "2026-11-01T03:12:02Z"}
|
|
5
|
+
{"EventID": 4624, "TargetUserName": "admin", "timestamp": "2026-11-01T03:12:03Z"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
title: Failed logon
|
|
2
|
+
id: 3b1f6c0e-7a52-4f3e-9d0a-1c2b3d4e5f04
|
|
3
|
+
name: failed_logon
|
|
4
|
+
status: experimental
|
|
5
|
+
description: A failed Windows logon. Used by the correlation below; not alerted on its own.
|
|
6
|
+
logsource:
|
|
7
|
+
product: windows
|
|
8
|
+
service: security
|
|
9
|
+
detection:
|
|
10
|
+
selection:
|
|
11
|
+
EventID: 4625
|
|
12
|
+
condition: selection
|
|
13
|
+
level: informational
|
|
14
|
+
---
|
|
15
|
+
title: Brute force against one account
|
|
16
|
+
id: 9e8d7c6b-5a49-4382-a1b0-c9d8e7f6a505
|
|
17
|
+
status: experimental
|
|
18
|
+
description: Three or more failed logons for the same user within five minutes.
|
|
19
|
+
correlation:
|
|
20
|
+
type: event_count
|
|
21
|
+
rules: [failed_logon]
|
|
22
|
+
group-by: [TargetUserName]
|
|
23
|
+
timespan: 5m
|
|
24
|
+
condition:
|
|
25
|
+
gte: 3
|
|
26
|
+
level: high
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"EventID": 1, "Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "ParentImage": "C:\\Windows\\explorer.exe", "CommandLine": "powershell.exe -NoProfile -enc SQBFAFgA"}
|
|
2
|
+
{"EventID": 1, "Image": "C:\\Program Files\\PowerShell\\7\\pwsh.exe", "ParentImage": "C:\\Windows\\explorer.exe", "CommandLine": "pwsh.exe /ec SQBFAFgA"}
|
|
3
|
+
{"EventID": 1, "Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\POWERSHELL.EXE", "ParentImage": "C:\\Windows\\System32\\cmd.exe", "CommandLine": "POWERSHELL -EncodedCommand SQBFAFgA "}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"EventID": 1, "Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "ParentImage": "C:\\Program Files\\Microsoft Configuration Manager\\bin\\ccmexec.exe", "CommandLine": "powershell.exe -enc SQBFAFgA"}
|
|
2
|
+
{"EventID": 1, "Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "ParentImage": "C:\\Windows\\explorer.exe", "CommandLine": "powershell.exe -File C:\\scripts\\encode.ps1"}
|
|
3
|
+
{"EventID": 1, "Image": "C:\\Windows\\System32\\cmd.exe", "ParentImage": "C:\\Windows\\explorer.exe", "CommandLine": "cmd.exe /c echo -enc "}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
title: PowerShell started with an encoded command
|
|
2
|
+
id: 5f0c2b2e-1d55-4c1a-9a51-7d1b0e3c6a01
|
|
3
|
+
status: experimental
|
|
4
|
+
description: Flags powershell.exe or pwsh.exe launched with -EncodedCommand or one of its abbreviations.
|
|
5
|
+
logsource:
|
|
6
|
+
product: windows
|
|
7
|
+
category: process_creation
|
|
8
|
+
detection:
|
|
9
|
+
selection_image:
|
|
10
|
+
Image|endswith:
|
|
11
|
+
- '\powershell.exe'
|
|
12
|
+
- '\pwsh.exe'
|
|
13
|
+
selection_flag:
|
|
14
|
+
CommandLine|windash|contains:
|
|
15
|
+
- ' -enc '
|
|
16
|
+
- ' -encodedcommand '
|
|
17
|
+
- ' -ec '
|
|
18
|
+
filter_trusted_parent:
|
|
19
|
+
ParentImage|startswith: 'C:\Program Files\Microsoft Configuration Manager\'
|
|
20
|
+
condition: all of selection_* and not filter_trusted_parent
|
|
21
|
+
level: high
|