Zion Boggan zionboggan.com ↗

caldera compose, validation matrix, readme with live detection screenshot

b5e426b   Zion Boggan committed on May 29, 2026 (3 weeks ago)
README.md +87 -0
@@ -0,0 +1,87 @@
+# Purple-Team Lab
+
+Emulate adversary techniques, then prove the detections fire. This is the validation
+half of [detection-as-code](https://github.com/zionboggan/detection-as-code): I run
+ATT&CK techniques against an instrumented Ubuntu endpoint enrolled in my
+[SOC automation lab](https://github.com/zionboggan/soc-automation-lab), and confirm
+each one raises the alert it's supposed to - mapped to the right technique, at a
+severity that would actually page someone.
+
+Writing a detection is a hypothesis. This is the test.
+
+![Emulated techniques detected in Wazuh](docs/screenshots/01-detections-fired.png)
+
+Every row is an atomic I executed on the endpoint and the rule that caught it - five of
+the six are detections I wrote (`100410`-`100413`) plus the brute-force rule, all on the
+`purple-target` agent, all ATT&CK-tagged.
+
+## How it's wired
+
+```mermaid
+flowchart LR
+ A[Atomic Red Team atomics<br/>run on purple-target VM] --> B[Wazuh agent<br/>auth log + real-time FIM]
+ B -->|1514/tcp| C[Wazuh manager<br/>analysisd: built-in + custom rules]
+ C --> D[Indexer]
+ D --> E[Dashboard<br/>ATT&CK-mapped alerts]
+ F[MITRE Caldera<br/>adversary emulation] -.-> B
+```
+
+The target is a dedicated Ubuntu 22.04 VM (not a container - kernel-level telemetry
+needs a real kernel). It runs the Wazuh agent shipping auth logs and **real-time file
+integrity monitoring** on the paths attackers use for persistence. Caldera is available
+for graph-based adversary emulation; the day-to-day validation uses Atomic Red Team
+because it maps cleanly one-atomic-to-one-detection.
+
+## Validation matrix
+
+Each technique was executed on the endpoint and confirmed in the SIEM:
+
+| Technique | Atomic | Telemetry | Rule | Level | Result |
+|-----------|--------|-----------|------|-------|--------|
+| T1110 Brute Force | 18 invalid SSH logins | auth log | `5712` (built-in) | 10 | ✅ detected |
+| T1053.003 Cron persistence | drop job in `/etc/cron.d` | FIM | `100410` (custom) | 10 | ✅ detected |
+| T1543.002 Systemd persistence | create `.service` unit | FIM | `100411` (custom) | 10 | ✅ detected |
+| T1098.004 SSH authorized_keys | append attacker key | FIM | `100412` (custom) | 12 | ✅ detected |
+| T1543 Tooling drop | binary into `/usr/local/bin` | FIM | `100413` (custom) | 10 | ✅ detected |
+| T1078 / T1548.003 | login + sudo to root | auth log | `5501` / `5402` | 3 | ✅ (informational baseline) |
+
+The custom rules are in [`rules/local_purple_rules.xml`](rules/local_purple_rules.xml);
+the atomics that exercise them are in [`atomics/run_atomics.sh`](atomics/run_atomics.sh).
+
+## Why FIM for persistence
+
+The persistence detections key off **file integrity monitoring**, not syscall auditing.
+That's deliberate: FIM is Wazuh-native and works everywhere - including containers and
+hardened hosts where the kernel audit framework isn't available to you - whereas auditd
+execve rules silently do nothing on a host that boots with audit disabled. The auditd
+ruleset I'd layer on a host that *does* have kernel auditing is included in
+[`agent/auditd-purple.rules`](agent/auditd-purple.rules), but the validated detections
+above don't depend on it.
+
+## Run it
+
+On the endpoint (enrolled Wazuh agent + the FIM config from `agent/`):
+
+```bash
+sudo bash atomics/run_atomics.sh # execute the technique set
+```
+
+Then in the Wazuh dashboard, filter Threat Hunting → Events to
+`rule.id:(100410 or 100411 or 100412 or 100413 or 5712)` and confirm the hits.
+
+Caldera (adversary-emulation UI, optional):
+
+```bash
+cd caldera && docker compose up -d # http://localhost:8888
+```
+
+## Layout
+
+```
+rules/local_purple_rules.xml custom FIM detections (deploy to the manager)
+agent/fim-directories.xml real-time FIM config for the agent's syscheck block
+agent/auditd-purple.rules auditd rules for hosts with kernel auditing
+atomics/run_atomics.sh the ATT&CK technique set
+caldera/docker-compose.yml MITRE Caldera server
+docs/validation-matrix.md the matrix above, with notes per technique
+```
caldera/docker-compose.yml +18 -0
@@ -0,0 +1,18 @@
+name: caldera
+
+services:
+ caldera:
+ image: caldera:local
+ build:
+ context: https://github.com/mitre/caldera.git#master
+ hostname: caldera
+ restart: unless-stopped
+ ports:
+ - "8888:8888"
+ - "8443:8443"
+ - "7010:7010"
+ - "7011:7011/udp"
+ - "7012:7012"
+ environment:
+ - CALDERA_CONF=local
+ command: ["--insecure", "--build"]
docs/screenshots/01-detections-fired.png +0 -0
Binary file not shown
docs/validation-matrix.md +26 -0
@@ -0,0 +1,26 @@
+# Validation matrix
+
+Each technique was executed on the `purple-target` endpoint and confirmed in the Wazuh
+SIEM. Result column reflects an actual run (see the screenshot in the README).
+
+| # | Technique | ATT&CK | Atomic action | Telemetry | Detection rule | Level | Detected |
+|---|-----------|--------|---------------|-----------|----------------|-------|----------|
+| 1 | Brute Force | T1110 | 18 invalid SSH logins from one source | sshd auth log | `5712` (built-in) | 10 | yes |
+| 2 | Scheduled Task / Cron | T1053.003 | write job to `/etc/cron.d/` | FIM (real-time) | `100410` (custom) | 10 | yes |
+| 3 | Systemd Service | T1543.002 | create unit in `/etc/systemd/system/` | FIM (real-time) | `100411` (custom) | 10 | yes |
+| 4 | SSH Authorized Keys | T1098.004 | append key to `~/.ssh/authorized_keys` | FIM (real-time) | `100412` (custom) | 12 | yes |
+| 5 | Create/Modify System Process | T1543 | drop binary in `/usr/local/bin/` | FIM (real-time) | `100413` (custom) | 10 | yes |
+| 6 | Valid Accounts / Sudo | T1078 / T1548.003 | login + sudo to root | sshd/PAM auth log | `5501` / `5402` (built-in) | 3 | yes (baseline) |
+
+## Notes
+
+- **Severity is intentional.** The persistence detections are level 10-12 (would alert),
+ while the login/sudo baseline events are level 3 (context, not pages). A detection that
+ fires at the wrong severity is as useless as one that doesn't fire.
+- **Coverage gap, honestly noted:** execve-based detections (e.g. reverse-shell command
+ lines, T1059.004) need host syscall auditing, which the target's kernel didn't provide.
+ On a host with working auditd, the rules in `agent/auditd-purple.rules` cover that path;
+ the FIM-based persistence detections above are independent of it.
+- **Promotion:** detections validated here are written once in Sigma in the
+ [detection-as-code](https://github.com/zionboggan/detection-as-code) repo and compiled
+ to each SIEM; the Wazuh-native versions live here.