Part three of the MITRE ATT&CK series. With the dataset mapped from Part 2, the natural next step is visualisation. MITRE ATT&CK Navigator is the canonical tool for it — a web app that renders the ATT&CK matrix and accepts JSON "layer" files describing which techniques to highlight, in what colour, with what annotation. This post walks through generating Navigator layers from the mapped data, so coverage maps, group footprints, and gap analyses produce themselves.
Key Takeaways
- MITRE ATT&CK Navigator consumes JSON "layer" files conforming to the published
layer.jsonschema (current version 4.5). - A layer is essentially a list of
(technique_id, color, comment)entries plus matrix-wide display options. - The three highest-value layers to generate from mapped data: groups heatmap, mitigations coverage, and detection coverage (technique IDs you have a SIEM rule for).
- Heatmap colours should reflect the data distribution, not a fixed scale. Use quantiles over the count series rather than equal-width buckets.
- Host Navigator yourself (it is a small Angular app) for an air-gapped workflow; otherwise load layers into the public instance at
mitre-attack.github.io/attack-navigator.
Environment
- Mapped technique data from Part 2.
- Python 3.10+ (uses
statistics.quantiles). - Public MITRE ATT&CK Navigator at mitre-attack.github.io/attack-navigator, or a self-hosted instance.
The Problem
Numbers are useful; pictures are persuasive. A defender-friendly question like "how much of the ATT&CK matrix do our detections cover?" is impossible to answer convincingly from a table. The same data, rendered as a coloured heatmap over the matrix, makes the gaps obvious to anyone in the room — including stakeholders who do not know what a technique ID is. Generating the layer JSON is straightforward once you understand the schema and pick the right colour scale.
The Solution
Step 1 — Compute per-technique counts
Each layer needs one number per technique. Pull them from the mapped data — groups using the technique, mitigations covering it, internal detections referencing it:
def per_technique_counts(mapped: list[dict], key: str) -> dict[str, int]:
"""key in ('groups','mitigations'); returns {technique_id: count}"""
return {t['technique_id']: len(t.get(key, [])) for t in mapped}
groups_count = per_technique_counts(mapped, 'groups')
mitig_count = per_technique_counts(mapped, 'mitigations')
Step 2 — Pick colour thresholds from the data distribution
Equal-width bins look terrible when the count distribution is skewed (and it always is — a handful of techniques are used by 70+ groups, the median is 2). Quantile-based bins reflect the data:
from statistics import quantiles
def colour_buckets(counts: dict[str, int]) -> list[tuple[int, str]]:
values = [v for v in counts.values() if v > 0]
if not values:
return [(0, '#ffffff')]
qs = quantiles(values, n=4)
palette = ['#ffe5e5', '#ff9999', '#ff4d4d', '#cc0000', '#660000']
thresholds = sorted({0, round(qs[0]), round(qs[1]), round(qs[2]), max(values)})
return list(zip(thresholds, palette[:len(thresholds)]))
def colour_for(count: int, buckets: list[tuple[int, str]]) -> str:
chosen = buckets[0][1]
for thr, col in buckets:
if count >= thr:
chosen = col
return chosen
Step 3 — Emit a layer file
The layer schema is documented in the Navigator layer format reference. The boilerplate is minimal:
def build_layer(name: str, counts: dict[str, int]) -> dict:
buckets = colour_buckets(counts)
return {
'name': name,
'description': f'Heatmap of {name} per technique',
'domain': 'enterprise-attack',
'versions': { 'attack': '15', 'navigator': '5.0.0', 'layer': '4.5' },
'gradient': { 'colors': [c for _, c in buckets], 'minValue': 0, 'maxValue': max(counts.values()) or 1 },
'legendItems': [ { 'label': str(thr), 'color': col } for thr, col in buckets ],
'techniques': [
{
'techniqueID': tid,
'color': colour_for(count, buckets),
'comment': f'{count} {name.lower()}',
'enabled': True,
}
for tid, count in counts.items()
],
'layout': { 'layout': 'flat', 'showName': True, 'showID': False, 'expandedSubtechniques': True },
'showTacticRowBackground': True,
'tacticRowBackground': '#dddddd',
'hideDisabled': True,
}
Step 4 — Generate the standard three layers
Three layers cover most stakeholder conversations: groups heatmap (volume of attacker usage), mitigations coverage (defensive options on paper), and your own detection coverage (what your SIEM actually catches):
import json
from pathlib import Path
def write_layer(layer: dict, path: Path) -> None:
path.write_text(json.dumps(layer, ensure_ascii=False, indent=2), encoding='utf-8')
out = Path('navigator_layers')
out.mkdir(exist_ok=True)
write_layer(build_layer('Groups', groups_count), out / 'groups.json')
write_layer(build_layer('Mitigations', mitig_count), out / 'mitigations.json')
# Detection coverage layer: load your own technique IDs from a CSV / SIEM export
detection_count = {tid: 1 for tid in detected_technique_ids}
write_layer(build_layer('Detection Coverage', detection_count), out / 'detection.json')
Step 5 — Load the layer in Navigator
Open mitre-attack.github.io/attack-navigator, click Create New Layer → Open Existing Layer, and upload the JSON file. The matrix renders with the colours and counts you generated. For a hosted internal instance, clone mitre-attack/attack-navigator, build with npm, and serve the static output behind your usual reverse proxy.
Step 6 — Read the picture and act on it
The three layers answer three distinct questions:
- Groups heatmap — where attackers concentrate effort.
T1059.001(PowerShell) andT1566.001(Spearphishing Attachment) are perpetual heat sinks; the long tail is interesting too. - Mitigations coverage — where MITRE documents defensive options. Light cells with high group usage are gaps in the framework itself, often interesting research targets.
- Detection coverage — where you actually have a SIEM rule, a YARA signature, or an EDR alert. The overlap with the groups heatmap is your real coverage posture; the gap between them is your detection roadmap.
Frequently Asked Questions
Why not just take a screenshot from the ATT&CK website?
Because the website does not know which techniques you have detections for or which mitigations you have actually implemented. Navigator with custom layers does. The website is for browsing the framework; Navigator is for representing your environment against it.
Can I overlay two layers?
Yes — Navigator supports layer scoring with a formula bar. Open both layers, then use the formula bar at the top to combine them (e.g. a + b). Useful for visualising "techniques used by groups AND covered by my detections" in one pass.
What is the difference between technique colour and tactic colour?
Technique colours come from the layer file's techniques array. Tactic colours (the column headers) are unrelated and set by Navigator itself. The tacticRowBackground option in the layer controls the row separator colour, not the tactic header.
How do I publish a layer to colleagues who do not have Navigator open?
Two options. Either export a PNG/SVG from Navigator's Layer Controls → Export menu, or host the layer JSON on an internal URL and share a Navigator link with ?layerURL=https://... in the query string — anyone with access to both Navigator and the URL gets the same view.
What schema version should I target?
The current schema is 4.5, supported by Navigator 4.x and 5.x. The schema is backward compatible, so older Navigator instances may load newer layers with reduced features. Target the lowest version that includes the features you actually use.
Conclusion
A Navigator layer is just a JSON file with technique IDs, colours, and comments. Once you can generate one from mapped data, the same pipeline produces every coverage view you will ever need: by group, by mitigation, by data source, by detection rule. The hard work is honest accounting of which techniques you actually catch; the picture is just the byproduct.
Related Posts
- Getting Started with MITRE ATT&CK: Fetching and Processing Data — part 1 of the series.
- Mapping with MITRE ATT&CK: Connecting Techniques, Groups, and Mitigations — part 2, the data source for these layers.
- MITRE ATT&CK + D3FEND: Mapping Defense to Attack — part 4, bridging the offensive layers into defensive countermeasures.
Authoritative references: MITRE ATT&CK Navigator on GitHub and the layer format specification.
Editorial note: posts on this blog are drafted with AI assistance and then reviewed, edited, and tested against a real environment before publishing. Commands, output, and screenshots come from systems I actually ran the work on.
0 comments:
Post a Comment