Detections¶
Every detection here started as a finding from a purple-team operation, and every one has tests: attack events it must fire on and normal events it must stay quiet on. CI runs the tests on every change.
From a miss to a tested detection¶
In OP-002 and OP-003, three things got past the SOC. OP-004 turned each one into a deployed detection:
| What happened | Why nothing alerted | The fix | |
|---|---|---|---|
| D1 | Attacker ran native discovery (whoami, net, nltest, systeminfo...) |
Each command is normal on its own; existing rules looked for attacker tools, not behavior | Alert when 4+ different discovery tools run on one host within 10 minutes |
| D2 | Attacker hopped from one workstation to another with valid credentials | The lateral-movement rule only watched traffic from the attack network | Alert on any network or RDP logon between two workstations |
| D3 | A Run-key persistence alert fired on reg.exe |
The alert title said "script host", which pointed the analyst the wrong way | Renamed the alert to "Run/RunOnce Key Modified", showing the actual writer |
Each was validated both ways in the lab before being enabled:
- D1 fired at 8 distinct tools on a replayed discovery burst; the busiest normal 10-minute window on any host had 1.
- D2 caught the OP-003 hop, and only that: of 8,864 network/RDP logons over 7 days, 4,028 were loopback and 4,835 went to the domain controller, leaving exactly 1.
labuser, type 3.A blind re-run on 2026-09-30 then caught both D1 and D2 cold, in about 5 and 6 minutes. See Results.
Inventory¶
| Detection | ATT&CK | Formats | Origin |
|---|---|---|---|
| Clustered native discovery (D1) | T1033 · T1082 · T1016 · T1057 · T1087 · T1018 | Splunk · Sigma correlation | OP-002 → OP-004 |
| Workstation-to-workstation logon (D2) | T1021.002 · T1550.002 · T1078 | Splunk · Sigma | OP-003 → OP-004 |
| Run/RunOnce key written by script host or LOLBin (D3) | T1547.001 | Splunk · Sigma | OP-002 → OP-004 |
| Run/RunOnce key written by a script host | T1547.001 | Wazuh 510150 |
PB-012 · OP-001 |
| PowerShell with ExecutionPolicy Bypass | T1059.001 | Wazuh 510140 · Sigma |
PB-003 |
| Defender Tamper Protection blocked a change | T1562.001 | Wazuh 510142 · Sigma |
PB-005 · OP-001 |
| Rundll32 spawned by PowerShell | T1218.011 | Wazuh 510143 · Sigma |
PB-009 |
| Connection to a controlled IOC IP | (none on purpose) | Wazuh 510144 |
PB-010 · PB-017 · PB-019 |
| Brute force, password spray, lockout, success-after-brute-force | T1110.001 · T1110.003 · T1078 | Wazuh 510120–510122, 510130–510133 |
PB-001 · PB-002 |
Example: D2 in Sigma¶
detection:
selection_logon:
EventID: 4624
LogonType: [3, 10]
selection_target_workstation:
Computer: ['Win10.soc.lab', 'WIN11-OPS01.soc.lab', 'WIN11-PB026.soc.lab']
selection_source_workstation:
IpAddress: ['10.10.2.4', '10.10.2.9', '10.10.2.8']
filter_loopback:
IpAddress: ['127.0.0.1', '::1', '-']
condition: all of selection_* and not filter_loopback
And its tests: the OP-003 hop must fire; a workstation logging on to the domain controller, a loopback logon, a console logon and a failed logon must not.
How the testing works¶
- Sigma rules are parsed by pySigma, converted to Splunk, and checked against their sample events by a small evaluator built on pySigma's own parser. The correlation rule is tested with a sliding 10-minute window, including a burst that crosses a clock boundary.
- Wazuh rules are checked for unique IDs in the lab's range, a parent rule and an ATT&CK tag, and their field patterns are run against sample alerts.
- To show the tests catch mistakes, I broke rules on purpose (dropped an exclusion, raised a threshold, changed a process name). Each change made a test fail.
Known issues I found in my own rules
Publishing the live Wazuh file meant reading it line by line, then testing each
rule live. 510140 (ExecutionPolicy Bypass) turned out to be silently dead on
the newest endpoint: another rule pack had taken over its parent event, so it was
never evaluated. Four more rules had the same outcome for other reasons:
510130/510131 (brute force, spray) compared a source-IP field Wazuh never
fills in on Windows events; 510122 (lockout) hung off the logon-success rule,
which lockout events never reach; and 510132 ("successful login after brute
force") never checked for a successful logon at all. All are fixed and
re-validated live (2026-10-12), as is 510133, which was labeled "lateral
movement" but only counted three failed logons; it is now an honest
low-severity brute-force early warning. Each finding is written up with its
fixes in the detections README.
Limits
D2 v2 (2026-10-13) reads its workstations from a lookup and caught two live WIN11-PB026 hops that v1 would have missed. Testing also found that WIN11-OPS01 had been cloned from PB026 without Sysprep (same machine SID), so logons between the two always failed; PB026 was Sysprepped and the hop now works. The Wazuh parent rule and decoder only run on a real Wazuh manager, and the Splunk searches were validated against live lab data rather than in CI. Sample events are representative events based on lab validation runs, not raw log exports.



