Scenario 1 of 2 · Reactive DFIR
TollBooth — investigate the breach
A web server's credentials leaked. Find how, follow the leaked key into the cloud, and write the incident report.
1. Open a terminal. 2. cd ~/tollbooth. 3. Start the agent: claude.
4. Paste each prompt below, in order, in the same chat, so it remembers.
Type only the prompt text — no quotes.
~20 min per visit. Do the CORE phases (1, 2, 5) for the full story. DEEP phases (3, 4) are for a quiet moment or to run later — this page is yours to keep. No agent? Terminal fallback commands are at the bottom.
Network forensics
Paste this, then Enter:
You will see: an SSRF request to 169.254.169.254 and a leaked key starting with ASIA.
SSRF: GET /proxy?url=http://169.254.169.254/... → a JSON cred blob with an
AccessKeyId starting ASIA, and exactly one external attacker IP.
Reveal the answer
SSRF on /proxy (10.0.1.50) forced a fetch of 169.254.169.254
(IMDSv1) → role creds leaked. Key ASIAJ7A6EXAMPLEK3Y99 (role
acme-webapp-role). Injector: 203.0.113.66.
Cloud forensics — the pivot
Same chat:
You will see: recon calls, then 6 GetObject downloads of customer data by the leaked key.
The same key does recon (GetCallerIdentity, ListBuckets, IAM
listing) then a burst of GetObject — arriving from an OUTSIDE IP,
unlike the legit calls from the instance itself.
Reveal the answer
Same key from 203.0.113.66 (external) vs 10.0.1.50 (legit):
recon, then 6× GetObject on acme-customer-data
(payment_tokens.csv, ssn_index.parquet, db-dump). Attacker IP in
pcap and CloudTrail = proof of pivot.
Baseline vs. anomaly
Same chat:
You will see: legit calls come from the instance's own IP; the attacker's come from outside — catchable by origin, not just the key.
Legit baseline = the instance's own IP / console. Attacker = same key, different origin. The tell is location + burst, not the key value — so yes, findable without it.
Reveal the answer
Legit calls originate from the instance / console; the attacker reuses the key from an external IP in a tight burst. Detectable by origin + rate even with the key unknown.
Map it — and check the agent
Same chat:
You will see: a clean ATT&CK chain, plus CSF categories (PR.AA / PR.PS / DE.CM) and D3FEND countermeasures (D3-ITF / D3-OTF / D3-NTA) — and watch it correctly skip ATLAS/AI RMF instead of forcing them in, and catch it if it over-maps ("exfiltration/T1567" when the data only shows reads = T1530). Correct any of these — this is the whole point of the exercise.
A per-step chain of ATT&CK IDs. The agent may over-map (S3 reads tagged as exfiltration/T1567). Correct read = T1530 (data from cloud storage). The catch is the learning.
Reveal the answer
ATT&CK: T1552.005 (IMDS) → T1078.004 (stolen key) → T1580 / T1087.004 (discovery) → T1530 (S3 read). Not T1567 — no exfil-OUT event exists here; that's the agent's classic over-map to catch.
CSF 2.0: PR.AA (credential/identity exposure), PR.PS (platform-hardening gap = IMDSv1), DE.CM (nothing alerted on the burst — the real detection gap).
D3FEND: D3-ITF / D3-OTF (traffic-filtering fixes), D3-NTA (the detection approach itself).
ATLAS / AI RMF: do not apply — no AI system in this incident; the agent should say so, not force a tag.
F3 (fraud): a judgment call given the stolen payment tokens — "maybe, but this attack is not a fraud-scheme TTP" is the strong answer.
Incident report
Same chat:
You will see: SSRF → stolen creds → discovery → S3 read, and "enforce IMDSv2" as the one fix.
One chain, an ID per step, and a single fix (IMDSv2) that breaks the whole thing.
Reveal the answer
Enforce IMDSv2 (HttpTokens=required) — breaks the entire chain at step
one.
Explore without spoiling it
Want to poke at the raw data yourself, agent aside?
cat cloudtrail/*.json | jq -r '.Records[].sourceIPAddress' | sort -u # who is talking?
cat cloudtrail/*.json | jq -r '.Records[].userIdentity.accessKeyId' | sort | uniq -c
Terminal fallbacks (no agent)
tshark -r lab-tollbooth.pcap -Y 'http.request.uri contains "169.254"' -T fields -e ip.src
tcpdump -nA -r lab-tollbooth.pcap | grep -A6 AccessKeyId # the leaked JSON
cat cloudtrail/*.json | jq \
'.Records[]|select(.userIdentity.accessKeyId=="ASIAJ7A6EXAMPLEK3Y99")|{t:.eventTime,e:.eventName}'
Advanced — does it scale? (optional, if a queue is thin)
python3 generate-bigdata.py --events 1000000 --days 3
python3 bigquery.py --build
python3 bigquery.py --key ASIAJ7A6EXAMPLEK3Y99 # same 12 events out of ~1,000,000, in seconds
python3 bigquery.py --anomaly # finds the needle WITHOUT the key
Authorized & lawful use only. All data is synthetic and self-contained; no live systems are touched. Promotes the open-source Anthropic Cybersecurity Skills library (Apache-2.0); not affiliated with Anthropic PBC.