attack-mapper 0.4.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.
- attack_mapper-0.4.0/LICENSE +21 -0
- attack_mapper-0.4.0/PKG-INFO +191 -0
- attack_mapper-0.4.0/README.md +166 -0
- attack_mapper-0.4.0/attack_mapper/__init__.py +30 -0
- attack_mapper-0.4.0/attack_mapper/attack_loader.py +140 -0
- attack_mapper-0.4.0/attack_mapper/build_db.py +44 -0
- attack_mapper-0.4.0/attack_mapper/cli.py +94 -0
- attack_mapper-0.4.0/attack_mapper/coverage.py +190 -0
- attack_mapper-0.4.0/attack_mapper/data/attack_db.json +6545 -0
- attack_mapper-0.4.0/attack_mapper/plugins.py +72 -0
- attack_mapper-0.4.0/attack_mapper/renderers.py +652 -0
- attack_mapper-0.4.0/attack_mapper/sigma_parser.py +128 -0
- attack_mapper-0.4.0/attack_mapper.egg-info/PKG-INFO +191 -0
- attack_mapper-0.4.0/attack_mapper.egg-info/SOURCES.txt +22 -0
- attack_mapper-0.4.0/attack_mapper.egg-info/dependency_links.txt +1 -0
- attack_mapper-0.4.0/attack_mapper.egg-info/entry_points.txt +2 -0
- attack_mapper-0.4.0/attack_mapper.egg-info/requires.txt +4 -0
- attack_mapper-0.4.0/attack_mapper.egg-info/top_level.txt +1 -0
- attack_mapper-0.4.0/pyproject.toml +40 -0
- attack_mapper-0.4.0/setup.cfg +4 -0
- attack_mapper-0.4.0/tests/test_bug_regressions.py +171 -0
- attack_mapper-0.4.0/tests/test_coverage.py +89 -0
- attack_mapper-0.4.0/tests/test_html_output.py +81 -0
- attack_mapper-0.4.0/tests/test_plugins_filters.py +81 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jose (JoseArgento)
|
|
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.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: attack-mapper
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Map your Sigma detection rules onto the MITRE ATT&CK matrix and find coverage gaps.
|
|
5
|
+
Author: Jose (JoseArgento)
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/JoseArgento/attack-mapper
|
|
8
|
+
Project-URL: Repository, https://github.com/JoseArgento/attack-mapper
|
|
9
|
+
Project-URL: Issues, https://github.com/JoseArgento/attack-mapper/issues
|
|
10
|
+
Keywords: mitre,att-ck,sigma,detection-engineering,soc,blue-team
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: pyyaml>=6.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# attack-mapper ๐ฏ
|
|
27
|
+
|
|
28
|
+
[](https://github.com/JoseArgento/attack-mapper/actions/workflows/ci.yml)
|
|
29
|
+
[](https://github.com/JoseArgento/attack-mapper/actions/workflows/update-attack-db.yml)
|
|
30
|
+
[](https://pypi.org/project/attack-mapper/)
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
|
|
33
|
+
Map your **Sigma detection rules** onto the **MITRE ATT&CK** matrix and find your
|
|
34
|
+
coverage gaps โ fast, offline, and CI-friendly.
|
|
35
|
+
|
|
36
|
+
Part of a detection-engineering toolkit that pairs perfectly with a
|
|
37
|
+
*Detection-as-Code* pipeline (Sigma โ CI โ SIEM). It answers the question every
|
|
38
|
+
blue team eventually asks: *"Which ATT&CK techniques do my rules actually
|
|
39
|
+
cover, and where are the blind spots?"*
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- ๐ Extracts ATT&CK technique IDs from Sigma rules via:
|
|
44
|
+
- canonical `attack.<id>` tags,
|
|
45
|
+
- raw `T1059` tags,
|
|
46
|
+
- `attack.mitre.org/techniques/...` URLs in `references:`.
|
|
47
|
+
- ๐ Per-tactic coverage bars + overall coverage ratio in the terminal.
|
|
48
|
+
- ๐จ **Four HTML styles** to show off your coverage:
|
|
49
|
+
- `matrix` (default) โ tactic cards with technique chips,
|
|
50
|
+
- `rows` โ compact tactic progress bars,
|
|
51
|
+
- `heat` โ dense heatmap of every technique,
|
|
52
|
+
- `report` โ print-ready dossier with canonical vertical ATT&CK columns
|
|
53
|
+
and sub-techniques nested under their parents (PDF it for audits/portfolio).
|
|
54
|
+
- ๐ข Covered techniques (incl. parent techniques of covered sub-techniques)
|
|
55
|
+
render green; gaps render red; explicitly ignored ones render grey.
|
|
56
|
+
- ๐ **Scope filters** โ `--include` and `--ignore` narrow the analysis to the
|
|
57
|
+
techniques/tactics you care about (or exclude the ones you don't). The active
|
|
58
|
+
scope is shown in the report so readers know what was filtered.
|
|
59
|
+
- ๐
**Portfolio badge** (`--badge`) โ a shields.io-style SVG coverage badge.
|
|
60
|
+
- ๐งพ **JSON summary** (`--json`) โ machine-readable output for dashboards/CI.
|
|
61
|
+
- ๐งญ **ATT&CK Navigator export** โ every HTML report embeds an
|
|
62
|
+
"Export layer" button that downloads a ready-to-import Navigator layer.
|
|
63
|
+
- ๐งฑ Uses the **real MITRE ATT&CK enterprise dataset** (v19 structure:
|
|
64
|
+
Stealth + Defense Impairment). Revoked/deprecated techniques are
|
|
65
|
+
filtered out so the coverage denominator matches the official matrix
|
|
66
|
+
(15 tactics, 222 techniques, 475 sub-techniques). Shipped as a compact
|
|
67
|
+
JSON, no network needed at runtime.
|
|
68
|
+
- โ
Pure Python stdlib + `pyyaml`; tiny dependency footprint.
|
|
69
|
+
- ๐ค Non-zero exit when nothing maps โ use it as a **CI gate** in your
|
|
70
|
+
detection repo.
|
|
71
|
+
|
|
72
|
+
## Screenshots
|
|
73
|
+
|
|
74
|
+
| `report` (print-ready dossier) | `matrix` (cards) |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
|  |  |
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install attack-mapper
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For development:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python -m venv .venv && source .venv/bin/activate
|
|
88
|
+
pip install -e ".[dev]"
|
|
89
|
+
pytest
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Usage
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Map a folder of Sigma rules, print the terminal report, emit HTML (matrix)
|
|
96
|
+
attack-mapper rules/ --html sample/coverage_matrix.html
|
|
97
|
+
|
|
98
|
+
# Try the other HTML styles
|
|
99
|
+
attack-mapper rules/ --html sample/coverage_rows.html --style rows
|
|
100
|
+
attack-mapper rules/ --html sample/coverage_heat.html --style heat
|
|
101
|
+
attack-mapper rules/ --html sample/coverage_report.html --style report
|
|
102
|
+
|
|
103
|
+
# Scope the analysis: only Execution + T1053, ignore Reconnaissance
|
|
104
|
+
attack-mapper rules/ --include T1059 TA0002 --ignore Reconnaissance
|
|
105
|
+
|
|
106
|
+
# Portfolio artifacts: badge + JSON
|
|
107
|
+
attack-mapper rules/ --badge sample/coverage_badge.svg --json sample/coverage.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Run against the bundled sample rules:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
attack-mapper rules/ --html sample/coverage_matrix.html --badge sample/coverage_badge.svg
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Why not sigma2attack or DeTT&CT?
|
|
117
|
+
|
|
118
|
+
Both are excellent and you should know they exist. **sigma2attack** (from the
|
|
119
|
+
Sigma tooling) converts Sigma rules into an ATT&CK Navigator heatmap layer โ
|
|
120
|
+
if all you need is a layer file, it does the job. **DeTT&CT** is the
|
|
121
|
+
heavyweight: it scores data-source *visibility* as well as detection
|
|
122
|
+
coverage, and is the right tool for a mature SOC doing formal capability
|
|
123
|
+
assessments.
|
|
124
|
+
|
|
125
|
+
attack-mapper sits in between, optimized for Detection-as-Code pipelines:
|
|
126
|
+
it runs offline with a bundled dataset, produces self-contained HTML
|
|
127
|
+
reports you can email or print (plus the Navigator layer, a badge, and
|
|
128
|
+
JSON), gates CI with its exit code, and supports scope filters for
|
|
129
|
+
counting only the techniques relevant to your environment. It is also
|
|
130
|
+
**ATT&CK v19-native** (Stealth + Defense Impairment) with a scheduled
|
|
131
|
+
workflow that flags future MITRE updates automatically.
|
|
132
|
+
|
|
133
|
+
## How it works
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Sigma rules โโโถ sigma_parser โโโถ technique IDs
|
|
137
|
+
โ
|
|
138
|
+
ATT&CK DB โโโถ attack_loader โ
|
|
139
|
+
โผ
|
|
140
|
+
coverage.build_report (with --include/--ignore) โโโถ CoverageReport
|
|
141
|
+
โ
|
|
142
|
+
renderers (terminal / HTML matrix|rows|heat)
|
|
143
|
+
plugins (badge SVG / JSON)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Scope filters
|
|
147
|
+
|
|
148
|
+
`--include` and `--ignore` accept technique IDs (e.g. `T1059`), sub-technique
|
|
149
|
+
prefixes (e.g. `T1059` also covers `T1059.001`), tactic shortnames
|
|
150
|
+
(e.g. `execution`) or tactic IDs (e.g. `TA0002`). A covered technique is always
|
|
151
|
+
recorded as covered even if it falls outside an `--include` scope, but only
|
|
152
|
+
in-scope covered techniques count toward the coverage ratio (both numerator
|
|
153
|
+
and denominator are scoped).
|
|
154
|
+
|
|
155
|
+
## Updating the ATT&CK dataset
|
|
156
|
+
|
|
157
|
+
The shipped `attack_mapper/data/attack_db.json` is a snapshot (regenerated
|
|
158
|
+
automatically by a monthly GitHub Action that fails when MITRE ships an update). To regenerate from the latest
|
|
159
|
+
official STIX bundle:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
curl -L -o enterprise-attack.json \
|
|
163
|
+
https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json
|
|
164
|
+
ATTACK_MAPPER_STIX=enterprise-attack.json python -m attack_mapper.build_db
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
You can also run directly off the STIX bundle at runtime by setting
|
|
168
|
+
`ATTACK_MAPPER_STIX`.
|
|
169
|
+
|
|
170
|
+
## Project layout
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
attack-mapper/
|
|
174
|
+
โโโ attack_mapper/
|
|
175
|
+
โ โโโ cli.py # argparse entry point
|
|
176
|
+
โ โโโ sigma_parser.py # Sigma โ ATT&CK technique IDs
|
|
177
|
+
โ โโโ attack_loader.py # ATT&CK DB (compact JSON / STIX)
|
|
178
|
+
โ โโโ coverage.py # coverage + gaps + scope filters
|
|
179
|
+
โ โโโ renderers.py # terminal table + 4 HTML styles
|
|
180
|
+
โ โโโ plugins.py # badge SVG + JSON summary
|
|
181
|
+
โ โโโ build_db.py # regenerate the compact ATT&CK DB
|
|
182
|
+
โ โโโ data/attack_db.json # real ATT&CK (compact, ships with the package)
|
|
183
|
+
โโโ rules/ # sample Sigma rules
|
|
184
|
+
โโโ sample/ # generated HTML / badge / JSON examples
|
|
185
|
+
โโโ tests/ # pytest suite
|
|
186
|
+
โโโ pyproject.toml
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# attack-mapper ๐ฏ
|
|
2
|
+
|
|
3
|
+
[](https://github.com/JoseArgento/attack-mapper/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/JoseArgento/attack-mapper/actions/workflows/update-attack-db.yml)
|
|
5
|
+
[](https://pypi.org/project/attack-mapper/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Map your **Sigma detection rules** onto the **MITRE ATT&CK** matrix and find your
|
|
9
|
+
coverage gaps โ fast, offline, and CI-friendly.
|
|
10
|
+
|
|
11
|
+
Part of a detection-engineering toolkit that pairs perfectly with a
|
|
12
|
+
*Detection-as-Code* pipeline (Sigma โ CI โ SIEM). It answers the question every
|
|
13
|
+
blue team eventually asks: *"Which ATT&CK techniques do my rules actually
|
|
14
|
+
cover, and where are the blind spots?"*
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- ๐ Extracts ATT&CK technique IDs from Sigma rules via:
|
|
19
|
+
- canonical `attack.<id>` tags,
|
|
20
|
+
- raw `T1059` tags,
|
|
21
|
+
- `attack.mitre.org/techniques/...` URLs in `references:`.
|
|
22
|
+
- ๐ Per-tactic coverage bars + overall coverage ratio in the terminal.
|
|
23
|
+
- ๐จ **Four HTML styles** to show off your coverage:
|
|
24
|
+
- `matrix` (default) โ tactic cards with technique chips,
|
|
25
|
+
- `rows` โ compact tactic progress bars,
|
|
26
|
+
- `heat` โ dense heatmap of every technique,
|
|
27
|
+
- `report` โ print-ready dossier with canonical vertical ATT&CK columns
|
|
28
|
+
and sub-techniques nested under their parents (PDF it for audits/portfolio).
|
|
29
|
+
- ๐ข Covered techniques (incl. parent techniques of covered sub-techniques)
|
|
30
|
+
render green; gaps render red; explicitly ignored ones render grey.
|
|
31
|
+
- ๐ **Scope filters** โ `--include` and `--ignore` narrow the analysis to the
|
|
32
|
+
techniques/tactics you care about (or exclude the ones you don't). The active
|
|
33
|
+
scope is shown in the report so readers know what was filtered.
|
|
34
|
+
- ๐
**Portfolio badge** (`--badge`) โ a shields.io-style SVG coverage badge.
|
|
35
|
+
- ๐งพ **JSON summary** (`--json`) โ machine-readable output for dashboards/CI.
|
|
36
|
+
- ๐งญ **ATT&CK Navigator export** โ every HTML report embeds an
|
|
37
|
+
"Export layer" button that downloads a ready-to-import Navigator layer.
|
|
38
|
+
- ๐งฑ Uses the **real MITRE ATT&CK enterprise dataset** (v19 structure:
|
|
39
|
+
Stealth + Defense Impairment). Revoked/deprecated techniques are
|
|
40
|
+
filtered out so the coverage denominator matches the official matrix
|
|
41
|
+
(15 tactics, 222 techniques, 475 sub-techniques). Shipped as a compact
|
|
42
|
+
JSON, no network needed at runtime.
|
|
43
|
+
- โ
Pure Python stdlib + `pyyaml`; tiny dependency footprint.
|
|
44
|
+
- ๐ค Non-zero exit when nothing maps โ use it as a **CI gate** in your
|
|
45
|
+
detection repo.
|
|
46
|
+
|
|
47
|
+
## Screenshots
|
|
48
|
+
|
|
49
|
+
| `report` (print-ready dossier) | `matrix` (cards) |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
|  |  |
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install attack-mapper
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For development:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python -m venv .venv && source .venv/bin/activate
|
|
63
|
+
pip install -e ".[dev]"
|
|
64
|
+
pytest
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Map a folder of Sigma rules, print the terminal report, emit HTML (matrix)
|
|
71
|
+
attack-mapper rules/ --html sample/coverage_matrix.html
|
|
72
|
+
|
|
73
|
+
# Try the other HTML styles
|
|
74
|
+
attack-mapper rules/ --html sample/coverage_rows.html --style rows
|
|
75
|
+
attack-mapper rules/ --html sample/coverage_heat.html --style heat
|
|
76
|
+
attack-mapper rules/ --html sample/coverage_report.html --style report
|
|
77
|
+
|
|
78
|
+
# Scope the analysis: only Execution + T1053, ignore Reconnaissance
|
|
79
|
+
attack-mapper rules/ --include T1059 TA0002 --ignore Reconnaissance
|
|
80
|
+
|
|
81
|
+
# Portfolio artifacts: badge + JSON
|
|
82
|
+
attack-mapper rules/ --badge sample/coverage_badge.svg --json sample/coverage.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Run against the bundled sample rules:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
attack-mapper rules/ --html sample/coverage_matrix.html --badge sample/coverage_badge.svg
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Why not sigma2attack or DeTT&CT?
|
|
92
|
+
|
|
93
|
+
Both are excellent and you should know they exist. **sigma2attack** (from the
|
|
94
|
+
Sigma tooling) converts Sigma rules into an ATT&CK Navigator heatmap layer โ
|
|
95
|
+
if all you need is a layer file, it does the job. **DeTT&CT** is the
|
|
96
|
+
heavyweight: it scores data-source *visibility* as well as detection
|
|
97
|
+
coverage, and is the right tool for a mature SOC doing formal capability
|
|
98
|
+
assessments.
|
|
99
|
+
|
|
100
|
+
attack-mapper sits in between, optimized for Detection-as-Code pipelines:
|
|
101
|
+
it runs offline with a bundled dataset, produces self-contained HTML
|
|
102
|
+
reports you can email or print (plus the Navigator layer, a badge, and
|
|
103
|
+
JSON), gates CI with its exit code, and supports scope filters for
|
|
104
|
+
counting only the techniques relevant to your environment. It is also
|
|
105
|
+
**ATT&CK v19-native** (Stealth + Defense Impairment) with a scheduled
|
|
106
|
+
workflow that flags future MITRE updates automatically.
|
|
107
|
+
|
|
108
|
+
## How it works
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Sigma rules โโโถ sigma_parser โโโถ technique IDs
|
|
112
|
+
โ
|
|
113
|
+
ATT&CK DB โโโถ attack_loader โ
|
|
114
|
+
โผ
|
|
115
|
+
coverage.build_report (with --include/--ignore) โโโถ CoverageReport
|
|
116
|
+
โ
|
|
117
|
+
renderers (terminal / HTML matrix|rows|heat)
|
|
118
|
+
plugins (badge SVG / JSON)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Scope filters
|
|
122
|
+
|
|
123
|
+
`--include` and `--ignore` accept technique IDs (e.g. `T1059`), sub-technique
|
|
124
|
+
prefixes (e.g. `T1059` also covers `T1059.001`), tactic shortnames
|
|
125
|
+
(e.g. `execution`) or tactic IDs (e.g. `TA0002`). A covered technique is always
|
|
126
|
+
recorded as covered even if it falls outside an `--include` scope, but only
|
|
127
|
+
in-scope covered techniques count toward the coverage ratio (both numerator
|
|
128
|
+
and denominator are scoped).
|
|
129
|
+
|
|
130
|
+
## Updating the ATT&CK dataset
|
|
131
|
+
|
|
132
|
+
The shipped `attack_mapper/data/attack_db.json` is a snapshot (regenerated
|
|
133
|
+
automatically by a monthly GitHub Action that fails when MITRE ships an update). To regenerate from the latest
|
|
134
|
+
official STIX bundle:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
curl -L -o enterprise-attack.json \
|
|
138
|
+
https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json
|
|
139
|
+
ATTACK_MAPPER_STIX=enterprise-attack.json python -m attack_mapper.build_db
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
You can also run directly off the STIX bundle at runtime by setting
|
|
143
|
+
`ATTACK_MAPPER_STIX`.
|
|
144
|
+
|
|
145
|
+
## Project layout
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
attack-mapper/
|
|
149
|
+
โโโ attack_mapper/
|
|
150
|
+
โ โโโ cli.py # argparse entry point
|
|
151
|
+
โ โโโ sigma_parser.py # Sigma โ ATT&CK technique IDs
|
|
152
|
+
โ โโโ attack_loader.py # ATT&CK DB (compact JSON / STIX)
|
|
153
|
+
โ โโโ coverage.py # coverage + gaps + scope filters
|
|
154
|
+
โ โโโ renderers.py # terminal table + 4 HTML styles
|
|
155
|
+
โ โโโ plugins.py # badge SVG + JSON summary
|
|
156
|
+
โ โโโ build_db.py # regenerate the compact ATT&CK DB
|
|
157
|
+
โ โโโ data/attack_db.json # real ATT&CK (compact, ships with the package)
|
|
158
|
+
โโโ rules/ # sample Sigma rules
|
|
159
|
+
โโโ sample/ # generated HTML / badge / JSON examples
|
|
160
|
+
โโโ tests/ # pytest suite
|
|
161
|
+
โโโ pyproject.toml
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""attack-mapper: map your Sigma detection rules onto the MITRE ATT&CK matrix."""
|
|
2
|
+
|
|
3
|
+
from .attack_loader import AttackDB, load_attack
|
|
4
|
+
from .coverage import CoverageReport, build_report
|
|
5
|
+
from .sigma_parser import (
|
|
6
|
+
ParsedRule,
|
|
7
|
+
parse_rule_file,
|
|
8
|
+
parse_rule_text,
|
|
9
|
+
parse_rules,
|
|
10
|
+
)
|
|
11
|
+
from .renderers import render_terminal, render_html, STYLES
|
|
12
|
+
from .plugins import render_badge, render_json
|
|
13
|
+
|
|
14
|
+
__version__ = "0.4.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AttackDB",
|
|
18
|
+
"load_attack",
|
|
19
|
+
"CoverageReport",
|
|
20
|
+
"build_report",
|
|
21
|
+
"ParsedRule",
|
|
22
|
+
"parse_rule_file",
|
|
23
|
+
"parse_rule_text",
|
|
24
|
+
"parse_rules",
|
|
25
|
+
"render_terminal",
|
|
26
|
+
"render_html",
|
|
27
|
+
"STYLES",
|
|
28
|
+
"render_badge",
|
|
29
|
+
"render_json",
|
|
30
|
+
]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Load MITRE ATT&CK data for coverage mapping.
|
|
2
|
+
|
|
3
|
+
Two modes are supported:
|
|
4
|
+
|
|
5
|
+
* **Bundled compact DB** (``data/attack_db.json``) โ generated by
|
|
6
|
+
:mod:`attack_mapper.build_db` from the official MITRE CTI STIX bundle. This
|
|
7
|
+
is the default because it is small (``~180 KB``) and needs no network.
|
|
8
|
+
* **Full STIX bundle** (``enterprise-attack.json``) โ if you point
|
|
9
|
+
``ATTACK_MAPPER_STIX`` at the raw STIX 2.0/2.1 bundle, it is parsed directly
|
|
10
|
+
so you always have the latest techniques/tactics.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
DEFAULT_DB_PATH = os.path.join(os.path.dirname(__file__), "data", "attack_db.json")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class AttackDB:
|
|
25
|
+
"""In-memory view of ATT&CK tactics and techniques."""
|
|
26
|
+
|
|
27
|
+
version: str = ""
|
|
28
|
+
tactics: Dict[str, dict] = field(default_factory=dict)
|
|
29
|
+
techniques: Dict[str, dict] = field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
def technique_name(self, tid: str) -> Optional[str]:
|
|
32
|
+
t = self.techniques.get(tid.upper())
|
|
33
|
+
return t["name"] if t else None
|
|
34
|
+
|
|
35
|
+
def tactic_name(self, shortname: str) -> Optional[str]:
|
|
36
|
+
t = self.tactics.get(shortname)
|
|
37
|
+
return t["name"] if t else shortname
|
|
38
|
+
|
|
39
|
+
def valid_technique(self, tid: str) -> bool:
|
|
40
|
+
return tid.upper() in self.techniques
|
|
41
|
+
|
|
42
|
+
def resolve_tactic_token(self, token: str) -> str:
|
|
43
|
+
"""Map a TA tactic ID (e.g. ``TA0002``) to its shortname.
|
|
44
|
+
|
|
45
|
+
Returns the token unchanged if it isn't a known TA id, so scope
|
|
46
|
+
tokens can be technique IDs, shortnames, or TA ids interchangeably.
|
|
47
|
+
Older compact DBs may lack the ``id`` field โ fall back to parsing
|
|
48
|
+
the TA id out of the tactic URL.
|
|
49
|
+
"""
|
|
50
|
+
wanted = token.strip().upper()
|
|
51
|
+
if not wanted.startswith("TA"):
|
|
52
|
+
return token
|
|
53
|
+
for shortname, tac in self.tactics.items():
|
|
54
|
+
taid = tac.get("id")
|
|
55
|
+
if not taid:
|
|
56
|
+
url = tac.get("url") or ""
|
|
57
|
+
if "/tactics/" in url:
|
|
58
|
+
taid = url.rstrip("/").rsplit("/", 1)[-1]
|
|
59
|
+
if taid and taid.upper() == wanted:
|
|
60
|
+
return shortname
|
|
61
|
+
return token
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _load_compact(path: str) -> AttackDB:
|
|
65
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
66
|
+
d = json.load(fh)
|
|
67
|
+
return AttackDB(
|
|
68
|
+
version=d.get("version", ""),
|
|
69
|
+
tactics=d.get("tactics", {}),
|
|
70
|
+
techniques=d.get("techniques", {}),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _load_stix(path: str) -> AttackDB:
|
|
75
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
76
|
+
bundle = json.load(fh)
|
|
77
|
+
tactics: Dict[str, dict] = {}
|
|
78
|
+
techniques: Dict[str, dict] = {}
|
|
79
|
+
version = ""
|
|
80
|
+
for o in bundle.get("objects", []):
|
|
81
|
+
t = o.get("type")
|
|
82
|
+
# Revoked/deprecated objects are kept in the STIX bundle for history,
|
|
83
|
+
# but they are no longer part of ATT&CK โ including them would inflate
|
|
84
|
+
# the coverage denominator with techniques that don't exist anymore.
|
|
85
|
+
if o.get("revoked") or o.get("x_mitre_deprecated"):
|
|
86
|
+
continue
|
|
87
|
+
if t == "x-mitre-matrix":
|
|
88
|
+
# The bundle has no version object; the matrix name + last-modified
|
|
89
|
+
# date is the most honest snapshot identifier available.
|
|
90
|
+
modified = (o.get("modified") or "")[:10]
|
|
91
|
+
version = f'{o.get("name", "ATT&CK")} ({modified})' if modified else o.get("name", "")
|
|
92
|
+
elif t == "x-mitre-tactic":
|
|
93
|
+
sn = o.get("x_mitre_shortname")
|
|
94
|
+
ref = next(
|
|
95
|
+
(e for e in o.get("external_references", []) if e.get("source_name") == "mitre-attack"),
|
|
96
|
+
{},
|
|
97
|
+
)
|
|
98
|
+
tactics[sn] = {
|
|
99
|
+
"id": ref.get("external_id"),
|
|
100
|
+
"name": o.get("name"),
|
|
101
|
+
"shortname": sn,
|
|
102
|
+
"url": ref.get("url"),
|
|
103
|
+
}
|
|
104
|
+
elif t == "attack-pattern":
|
|
105
|
+
refs = [e for e in o.get("external_references", []) if e.get("source_name") == "mitre-attack"]
|
|
106
|
+
if not refs:
|
|
107
|
+
continue
|
|
108
|
+
ext = refs[0].get("external_id")
|
|
109
|
+
if not (ext and ext.startswith("T")):
|
|
110
|
+
continue
|
|
111
|
+
techniques[ext] = {
|
|
112
|
+
"id": ext,
|
|
113
|
+
"name": o.get("name"),
|
|
114
|
+
"url": refs[0].get("url"),
|
|
115
|
+
"tactics": [p.get("phase_name") for p in o.get("kill_chain_phases", [])],
|
|
116
|
+
"is_subtechnique": "." in ext,
|
|
117
|
+
}
|
|
118
|
+
return AttackDB(version=version or "stix", tactics=tactics, techniques=techniques)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def load_attack(db_path: Optional[str] = None) -> AttackDB:
|
|
122
|
+
"""Load ATT&CK data.
|
|
123
|
+
|
|
124
|
+
Resolution order:
|
|
125
|
+
1. ``db_path`` argument if provided.
|
|
126
|
+
2. ``ATTACK_MAPPER_STIX`` env var -> full STIX bundle.
|
|
127
|
+
3. ``ATTACK_MAPPER_DB`` env var -> compact DB.
|
|
128
|
+
4. Bundled compact DB shipped with the package.
|
|
129
|
+
"""
|
|
130
|
+
stix = os.environ.get("ATTACK_MAPPER_STIX")
|
|
131
|
+
compact = db_path or os.environ.get("ATTACK_MAPPER_DB") or os.path.abspath(DEFAULT_DB_PATH)
|
|
132
|
+
|
|
133
|
+
if stix and os.path.isfile(stix):
|
|
134
|
+
return _load_stix(stix)
|
|
135
|
+
if os.path.isfile(compact):
|
|
136
|
+
return _load_compact(compact)
|
|
137
|
+
raise FileNotFoundError(
|
|
138
|
+
f"No ATT&CK database found. Looked for compact DB at {compact} "
|
|
139
|
+
f"and STIX at {stix}. Run `python -m attack_mapper.build_db` to generate one."
|
|
140
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Generate data/attack_db.json from a MITRE CTI STIX bundle.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
ATTACK_MAPPER_STIX=path/to/enterprise-attack.json \
|
|
5
|
+
python -m attack_mapper.build_db
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
from .attack_loader import _load_stix # noqa: F401 (reuse the parser)
|
|
14
|
+
|
|
15
|
+
DEFAULT_OUT = os.path.join(os.path.dirname(__file__), "data", "attack_db.json")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> int:
|
|
19
|
+
stix = os.environ.get("ATTACK_MAPPER_STIX")
|
|
20
|
+
if not stix or not os.path.isfile(stix):
|
|
21
|
+
print(
|
|
22
|
+
"Set ATTACK_MAPPER_STIX to a downloaded enterprise-attack.json "
|
|
23
|
+
"(e.g. https://github.com/mitre/cti).",
|
|
24
|
+
file=__import__("sys").stderr,
|
|
25
|
+
)
|
|
26
|
+
return 1
|
|
27
|
+
db = _load_stix(stix)
|
|
28
|
+
out = os.path.abspath(DEFAULT_OUT)
|
|
29
|
+
compact = {
|
|
30
|
+
"version": db.version,
|
|
31
|
+
"tactics": db.tactics,
|
|
32
|
+
"techniques": db.techniques,
|
|
33
|
+
}
|
|
34
|
+
with open(out, "w", encoding="utf-8") as fh:
|
|
35
|
+
json.dump(compact, fh, indent=1)
|
|
36
|
+
print(
|
|
37
|
+
f"Wrote {len(db.techniques)} techniques / {len(db.tactics)} tactics "
|
|
38
|
+
f"to {out}"
|
|
39
|
+
)
|
|
40
|
+
return 0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""CLI entry point for attack-mapper."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from .attack_loader import load_attack
|
|
10
|
+
from .coverage import build_report
|
|
11
|
+
from .renderers import render_terminal, render_html, STYLES
|
|
12
|
+
from .sigma_parser import parse_rules
|
|
13
|
+
from .plugins import render_badge, render_json
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
17
|
+
p = argparse.ArgumentParser(
|
|
18
|
+
prog="attack-mapper",
|
|
19
|
+
description="Map your Sigma detection rules onto the MITRE ATT&CK matrix "
|
|
20
|
+
"and find coverage gaps.",
|
|
21
|
+
)
|
|
22
|
+
p.add_argument(
|
|
23
|
+
"rules",
|
|
24
|
+
nargs="+",
|
|
25
|
+
help="Sigma rule file(s), directory(ies), or glob pattern(s).",
|
|
26
|
+
)
|
|
27
|
+
p.add_argument("--db", default=None, help="Path to a compact ATT&CK DB JSON.")
|
|
28
|
+
p.add_argument(
|
|
29
|
+
"--html", metavar="PATH", default=None,
|
|
30
|
+
help="Write a standalone HTML coverage report to PATH.",
|
|
31
|
+
)
|
|
32
|
+
p.add_argument(
|
|
33
|
+
"--style", choices=STYLES, default="matrix",
|
|
34
|
+
help="HTML layout: matrix (cards), rows (bars), or heat (dense).",
|
|
35
|
+
)
|
|
36
|
+
p.add_argument(
|
|
37
|
+
"--include", nargs="*", default=None, metavar="TOKEN",
|
|
38
|
+
help="Only count/show techniques or tactics matching these tokens "
|
|
39
|
+
"(e.g. T1059 TA0002). Others are treated as out-of-scope.",
|
|
40
|
+
)
|
|
41
|
+
p.add_argument(
|
|
42
|
+
"--ignore", nargs="*", default=None, metavar="TOKEN",
|
|
43
|
+
help="Exclude these techniques/tactics from scope (e.g. T1003 Reconnaissance).",
|
|
44
|
+
)
|
|
45
|
+
p.add_argument(
|
|
46
|
+
"--json", metavar="PATH", default=None,
|
|
47
|
+
help="Also write a machine-readable JSON summary to PATH.",
|
|
48
|
+
)
|
|
49
|
+
p.add_argument(
|
|
50
|
+
"--badge", metavar="PATH", default=None,
|
|
51
|
+
help="Also write an SVG coverage badge to PATH (great for a README).",
|
|
52
|
+
)
|
|
53
|
+
p.add_argument("--quiet", action="store_true", help="Suppress the terminal table.")
|
|
54
|
+
p.add_argument("--version", action="version", version="%(prog)s 0.4.0")
|
|
55
|
+
return p
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main(argv=None) -> int:
|
|
59
|
+
args = build_parser().parse_args(argv)
|
|
60
|
+
try:
|
|
61
|
+
db = load_attack(args.db)
|
|
62
|
+
except FileNotFoundError as exc:
|
|
63
|
+
print(str(exc), file=sys.stderr)
|
|
64
|
+
return 2
|
|
65
|
+
|
|
66
|
+
rules = parse_rules(args.rules)
|
|
67
|
+
if not rules:
|
|
68
|
+
print("No Sigma rules found at the given path(s).", file=sys.stderr)
|
|
69
|
+
return 1
|
|
70
|
+
|
|
71
|
+
report = build_report(rules, db, include=args.include, ignore=args.ignore)
|
|
72
|
+
|
|
73
|
+
if not args.quiet:
|
|
74
|
+
print(render_terminal(report))
|
|
75
|
+
|
|
76
|
+
if args.html:
|
|
77
|
+
out = render_html(report, args.html, style=args.style)
|
|
78
|
+
print(f"\nHTML report written to: {os.path.abspath(out)}")
|
|
79
|
+
|
|
80
|
+
if args.json:
|
|
81
|
+
with open(args.json, "w", encoding="utf-8") as fh:
|
|
82
|
+
fh.write(render_json(report))
|
|
83
|
+
print(f"JSON summary written to: {os.path.abspath(args.json)}")
|
|
84
|
+
|
|
85
|
+
if args.badge:
|
|
86
|
+
with open(args.badge, "w", encoding="utf-8") as fh:
|
|
87
|
+
fh.write(render_badge(report))
|
|
88
|
+
print(f"Badge written to: {os.path.abspath(args.badge)}")
|
|
89
|
+
|
|
90
|
+
return 0 if report.covered else 1
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
raise SystemExit(main())
|