SIEM alert triage — separating signal from noise under pressure
Inherited a Splunk ES instance drowning in false positives. Rebuilt the triage flow against a live stream of mixed alerts — brute force, impossible travel, benign scanners, DGA — and dropped mean-time-to-decision from 6 min to under 90 s.
Open alerts
12
MTTD
6m 41s
FP rate
18%
Escalations
3
Alert volume · daily
↓ 62% after detection tuning
MTTD (lab)
14m → 3m
FP rate
-61%
New searches
6
ATT&CK coverage
+33%
Cut analyst time-on-alert by tuning noisy detections and building a repeatable triage loop.
- 01 · Brief
- 02 · Your move
- 03 · Result
- 04 · How I stopped it
- 05 · How you prevent it
Six alerts just landed in the queue. Some are real, some are noise. You have one minute of analyst time budgeted per alert — real SOCs don't get more.
Tag each as TP (true positive, escalate) or FP (false positive, tune the rule).
Tools
- Splunk ES
- Sysmon
- MITRE ATT&CK Navigator
- PowerShell
Platforms
- Splunk
- Windows Server 2022
- Active Directory
- 01
Baselined the last 30 days of alerts; ranked by FP rate × analyst-minutes-per-alert.
- 02
Built a decision tree per detection type (auth, network, host, identity) so triage is checklist-driven.
Fig · 02 · Triage queue · disposition dispositionsLIVESentinel · Alert queueAssigned to op-sds-04Time Alert Sev Disposition 09:41 Impossible travel · a.patel HIGH True positive 09:38 PowerShell EncodedCommand HIGH Contained 09:22 Failed logon burst (svc_backup) MED Benign · pw rotation 09:04 New OAuth grant · unknown app HIGH True positive 08:51 Login from TOR exit MED Blocked by CA 08:12 AV: EICAR test file LOW Benign · training - 03
Wrote SPL for behavior-based signals — impossible travel with device-context guard, vssadmin canaries, DGA entropy.
Fig · 03 · Detection editor · tuned SPL ruleLIVEdetection-rules/impossible_travel.splSPL · tuned12345678910index=azuread sourcetype="SigninLogs" | where ResultType==0 | eval prev_ip=prev(IPAddress), prev_time=prev(TimeGenerated), prev_geo=prev(Location) | eval dist_km = geodistance(prev_geo, Location) | eval mins = (TimeGenerated - prev_time)/60 | where mins < 60 and dist_km > 500 | where UserPrincipalName !in (svc_travelers_lookup) // tuning | stats count by UserPrincipalName, prev_ip, IPAddress - 04
Re-emulated with Atomic Red Team and measured every detection end-to-end.
MTTD cut from 14 min to 3 min in lab; six new correlation searches; false-positive rate down 61%.
- Every noisy alert costs the whole SOC — killing FPs is a force multiplier.
- Decision trees make junior analysts as fast as senior ones on the top 80% of alerts.
Next case · NET-022
Packet inspection — hunting a C2 beacon inside noisy egress
Open →
