Incident report: attacking my own lab and grading my SIEM
I attacked my own isolated practice lab on purpose, in stages, like a real intruder would. Then I wrote it up the way a SOC analyst would: timeline, evidence, impact, and the gaps I found. Spoiler: the custom rule I wrote earlier caught every boolean SQL injection, and one attack got through without a single alert.
A controlled simulation, not a breach
This was a self-authorized lab exercise. The attacker and the targets were both machines I own. The targets were DVWA and OWASP Juice Shop, two apps that are vulnerable on purpose, running on a Docker network with no internet access. They’re reachable only from the server itself or over my private Tailscale network, and they’re switched off unless I’m testing. No production service, third party or public-facing system was touched.
The point wasn’t to break the apps, which are built to be broken. It was to measure what my Wazuh 4.14.9 SIEM actually detects, reading the lab’s nginx access logs, and to practice writing a proper incident report.
What I ran and what Wazuh saw
| Stage | Attack | Detection |
|---|---|---|
| Recon | nmap -sV -sC against the two lab apps | Rule 31151, level 10: many web 400s from one source (MITRE T1595.002) |
| SQLi (UNION) | 1' UNION SELECT user,password FROM users | Rule 31106, level 6: web attack returned 200 (T1190) |
| SQLi (boolean) ×3 | 1' OR '1'='1, 1' OR 1=1--, x' OR 'a'='a | Custom rule 100101, level 12, 3 of 3 (T1190) |
| Reflected XSS | <script>alert(1)</script> | Rule 31106, level 6 (T1190) |
| File inclusion | ../../../../etc/passwd | Rule 31106, level 6 (T1190) |
| Juice Shop SQLi (GET) | Product search apple')) UNION SELECT 1-- | Rule 31103, level 7: SQL injection attempt (T1190) |
| Juice Shop SQLi (POST) | Login bypass, ' OR 1=1-- in the JSON body | Missed: no alert |
The whole chain took about two seconds after the scan, and the alerts landed within a second of the attacks. I skipped an SSH brute-force stage on purpose: the lab has no SSH service, and I wasn’t going to aim it at my real server’s SSH.
The detection gap I closed held up
In my first SOC lab test, the boolean OR '1'='1' injection returned every user row and Wazuh said nothing, because the stock rules look for keywords like UNION or SELECT. I wrote custom rules 100100/100101 that match the shape of a boolean injection instead.
This time, all three boolean variants raised rule 100101 at level 12, the highest-severity alerts of the whole exercise, mapped to MITRE ATT&CK T1190 (Exploit Public-Facing Application). Recon was also caught by a correlation rule, not just a pile of individual 404s, which is the early “someone is scanning us” signal an analyst wants.
Where my pipeline is blind
- SQL injection in a POST body. The Juice Shop login bypass worked and raised nothing. nginx access logs record the method, URL and status code, not the request body, so a log-based SIEM never saw the payload. The fix is body inspection: a WAF like ModSecurity or Coraza with the OWASP Core Rule Set in front of the lab.
- No network-layer visibility. A plain TCP port scan with no web requests wouldn’t show up in these logs at all. Next step: Suricata as a network IDS, feeding Wazuh.
- Generic labels. XSS and file inclusion were both just “web attack returned 200.” They were detected, but an analyst would want them labelled as what they are, so I plan custom rules for those too.
Closing the case
Real impact: none. The “user table” the UNION injection dumped holds only DVWA’s demo accounts in a throwaway container. On a real app, the same injections would have exposed usernames and password hashes, which is why I rated the simulated severity High.
After the test I ran lab down, confirmed all four lab containers and both lab networks were gone and nothing was still listening on the lab ports, and deleted the temporary attack files. My SSH, firewall, VPN, Cloudflare and website configs were never touched.
The full report, with the timeline, sanitized alert JSON and every command I ran, is on GitHub: incident-report-simulated-attack.md.
What I learned
- Retest your fixes. A detection rule only counts once it fires on a live attack, not just in a test tool.
- Know what your logs can’t see. The miss wasn’t a bad rule. The data simply wasn’t there, and no rule can match a payload that never gets logged.
- Severity tells a story. Level 12 on a likely-successful injection versus level 6 on a generic hit is the difference between “look now” and “look later.”
- Write it up like it’s real. A timeline, evidence and lessons learned turn a lab exercise into something another analyst could act on.