diff --git a/CLAUDE.md b/CLAUDE.md index ab435098..53fcc056 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,7 +32,8 @@ Two kinds of document, edited differently: first, and a correction is a new entry that names the one it corrects. An accepted ADR in `docs/adr/` is immutable: a changed decision is a new ADR that supersedes it. An old ADR that says something has not happened yet - stays as written. + stays as written. An exercise in `docs/exercises/` is a record too: a re-run + is a new file. - **Current-state documents** must stay true: `README.md`, `docs/hardware.md`, `docs/network.md`, `docs/architecture.md`, `docs/security.md`, `docs/roadmap.md`, `docs/runbooks/` and diff --git a/README.md b/README.md index 01cf6cef..1dbd0d37 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,7 @@ inbound passes may still reach it. Data flow and the maintained Mermaid topology │ ├── observability.md security.md roadmap.md changelog.md │ ├── diagrams/ # the network diagram (SVG) and its predecessors │ ├── adr/ # 94 ADRs — architecture decision records +│ ├── exercises/ # purple-team exercise records — see its README │ └── runbooks/ # 49 runbooks; successor-handover.md is the front door └── Makefile # make help ``` diff --git a/docs/exercises/README.md b/docs/exercises/README.md new file mode 100644 index 00000000..80dc6d7c --- /dev/null +++ b/docs/exercises/README.md @@ -0,0 +1,47 @@ +# Exercises + +One file per purple-team exercise against the lab domain: what was run, +what the estate was expected to see, what it actually saw, and what changed +because of it. The point is the gap between *expected* and *observed*. An +exercise that finds no gap is still worth a file, because it proves a +detection works on a date. + +## The rules + +- **One exercise, one file**, named `YYYY-MM-DD-short-name.md`, copied from + [`TEMPLATE.md`](TEMPLATE.md). +- **An exercise file is a record.** Like [`changelog.md`](../changelog.md), it + is never rewritten after the day. A re-run is a new file that links the + earlier one, so the history shows a detection being fixed, not just the end + state. +- **Every technique carries its MITRE ATT&CK ID**, so exercises can be searched + by technique and coverage can be counted. +- **Only inside the lab.** Every target is on the lab domain or the range + ([ADR-0007](../adr/0007-defensive-estate-and-offensive-range.md), + [ADR-0014](../adr/0014-put-ifrit-on-imaginationlan-and-give-the-targets-no-route.md)). + Nothing here is run against the household tiers or anything outside the + house. +- **Describe the activity, not the payload.** Name the technique and the tool + and say what was done. No working exploit code, no captured credentials or + hashes, no tokens. This repository is public, and + [`security.md`](../security.md#what-this-repository-deliberately-does-not-publish) + applies here as everywhere. +- **A missed or weak detection opens an issue.** The issue is the follow-up; + the exercise file links it. + +## Where the evidence comes from + +| Source | Lives on | What it sees | +| --- | --- | --- | +| Wazuh | `odin` ([`stacks/soc`](../../stacks/soc)) | Agent alerts from the domain's hosts, Sysmon included | +| Velociraptor | `odin` ([`stacks/soc`](../../stacks/soc)) | Endpoint state, collected on demand | +| Zeek | `fenrir` ([`stacks/sensor`](../../stacks/sensor)) | Network metadata from the mirrored lab bridge | +| Suricata | `morpheus` | Alerts at the firewall, the chokepoint ([ADR-0006](../adr/0006-detect-at-the-chokepoint.md)) | +| Loki and Grafana | `alexander` ([`stacks/lab`](../../stacks/lab)) | The lab's logs, searchable by time window | + +## Index + +Newest first. Add a row with each new file. + +| Date | Exercise | Techniques | Result | +| --- | --- | --- | --- | diff --git a/docs/exercises/TEMPLATE.md b/docs/exercises/TEMPLATE.md new file mode 100644 index 00000000..9be98a24 --- /dev/null +++ b/docs/exercises/TEMPLATE.md @@ -0,0 +1,50 @@ +# YYYY-MM-DD: Short name of the exercise + +**Date:** YYYY-MM-DD · **Operator:** you · **Issue:** #N, if one exists · +**Re-run of:** link to an earlier exercise, or *none* + +## Objective + +One or two sentences: what this exercise tests, and which detection it is +meant to prove or disprove. + +## Scope + +- **Attacker:** the host or VM the activity came from. +- **Targets:** the lab hosts it touched. Lab domain or range only. +- **Window:** start and end time, with the time zone, so the logs can be + pulled for exactly this period. +- **Lab state:** anything switched on for this run, such as which deliberate + weakness tags were enabled + ([ADR-0083](../adr/0083-give-the-lab-domain-its-deliberate-weaknesses-as-switchable-tags.md)). + +## Techniques + +One row per step, in the order run. Describe the activity, not the payload. + +| # | Time | ATT&CK ID | Technique | Tool | What was done | +| --- | --- | --- | --- | --- | --- | +| 1 | hh:mm | T0000 | Name | Tool | One line | + +## Expected detections + +Written before the run, so the result cannot shape the expectation. + +| # | Source | Expected rule or signal | +| --- | --- | --- | +| 1 | Wazuh / Zeek / Suricata / Velociraptor / Loki | Rule ID or query, and what it should show | + +## Observed + +| # | Detected? | Where, and how long after the step | Notes | +| --- | --- | --- | --- | +| 1 | Yes / Partly / No | Source and delay | Alert text summarised, not pasted with secrets | + +## Gaps and fixes + +For every *No* or *Partly* above: why it was missed, and what changes. +Each fix links the issue or PR that makes it. + +## Lessons + +What this exercise taught, in a few lines.