SPL is the query language that makes Splunk useful for threat hunting. But writing good hunting queries from scratch takes time. Here are battle-tested SPL queries you can drop into your Splunk instance and start hunting today.
Fundamentals: How SPL Hunting Queries Work
Every SPL hunt follows this structure:
index=
| stats/eval/where
| sort/table
Start broad, filter aggressively, aggregate for patterns. The key commands:
-
stats: Aggregate and count. Your most-used command. -
eventstats: Add statistics without removing original events. -
transaction: Group related events into chains (e.g., a full attack sequence). -
tstats: Like stats, but runs on accelerated data models. 10x faster for large datasets.
1. Process Creation Anomalies
Processes spawned from temp directories
index=sysmon EventCode=1
| where like(Image, "%\\Temp\\%") OR like(Image, "%\\tmp\\%")
| stats count by Image, ParentImage, User, ComputerName
| where count < 5
| sort -count
Unusual parent-child relationships
index=sysmon EventCode=1
| stats count by ParentImage, Image
| where (like(ParentImage, "%\\winword.exe") OR like(ParentImage, "%\\excel.exe"))
AND NOT like(Image, "%\\splwow64.exe")
| sort -count
Word or Excel spawning anything other than splwow64.exe (print driver) is suspicious. This catches macro-based initial access.
LOLBins execution
index=sysmon EventCode=1
| search Image IN ("*\\certutil.exe","*\\mshta.exe","*\\regsvr32.exe",
"*\\rundll32.exe","*\\wmic.exe","*\\msbuild.exe","*\\cscript.exe",
"*\\wscript.exe","*\\bitsadmin.exe")
| stats count by Image, CommandLine, ParentImage, User
| sort -count
2. Lateral Movement
PsExec-style activity
index=windows EventCode=7045
| where like(Service_File_Name, "%PSEXESVC%")
OR like(Service_File_Name, "%\\ADMIN$\\%")
OR like(Service_File_Name, "%cmd.exe /c%")
| stats count by ComputerName, Service_Name, Service_File_Name, Account_Name
| sort -count
Remote service creation (generic)
index=windows EventCode=7045
| stats count by ComputerName, Service_Name, Service_File_Name
| where count=1
| sort -Service_Name
One-off service installations across multiple hosts often indicate lateral movement tools.
SMB lateral movement
index=sysmon EventCode=3
| where DestinationPort=445
| stats dc(DestinationIp) as unique_targets, values(DestinationIp) as targets
by SourceIp, Image
| where unique_targets > 5
| sort -unique_targets
More than 5 unique SMB targets from one source in a short window is worth investigating.
3. C2 Beaconing Detection
Interval analysis (coefficient of variation)
index=proxy OR index=firewall
| sort 0 src_ip, dest_ip, _time
| streamstats current=f last(_time) as prev_time by src_ip, dest_ip
| eval interval=_time-prev_time
| stats count, avg(interval) as avg_int, stdev(interval) as std_int
by src_ip, dest_ip
| eval cv=round(std_int/avg_int, 4)
| where cv < 0.1 AND count > 50
| sort cv
A coefficient of variation (CV) below 0.1 means the connection intervals are highly regular. Humans are irregular. Beacons are regular. This is one of the most effective C2 detection techniques.
Beaconing with jitter tolerance
index=proxy OR index=firewall
| bin _time span=10m
| stats count by src_ip, dest_ip, dest_port, _time
| stats count as total_bins, avg(count) as avg_conn by src_ip, dest_ip, dest_port
| where total_bins > 20 AND avg_conn > 0.8 AND avg_conn < 1.5
| sort -total_bins
4. DNS Tunneling
Abnormally long subdomains
index=dns
| eval subdomain_len=len(replace(query, "\.\w+\.\w+$", ""))
| where subdomain_len > 50
| stats count by query, src_ip
| sort -count
DNS tunneling tools like dnscat2 and iodine encode data in subdomain labels. Legitimate subdomains rarely exceed 30 characters.
High query volume to single domain
index=dns
| rex field=query "\.(?[^\.]+\.[^\.]+)$"
| stats count, dc(query) as unique_queries by src_ip, root_domain
| where unique_queries > 100 AND count > 500
| sort -unique_queries
5. Persistence Mechanisms
Scheduled task creation
index=sysmon EventCode=1
| where like(Image, "%\\schtasks.exe") AND like(CommandLine, "%/Create%")
| stats count by CommandLine, User, ComputerName, ParentImage
| sort -count
Registry run keys modification
index=sysmon EventCode=13
| where like(TargetObject, "%\\CurrentVersion\\Run%")
OR like(TargetObject, "%\\CurrentVersion\\RunOnce%")
| stats count by TargetObject, Details, Image, User
| sort -count
WMI persistence
index=sysmon EventCode=1
| where like(Image, "%\\wmic.exe") AND like(CommandLine, "%process call create%")
| stats count by CommandLine, User, ComputerName
| sort -count
Performance Tips
-
Filter by index first. Always specify
index=at the start. -
Use
tstatswhen possible. It runs on accelerated data models and is dramatically faster. -
Avoid leading wildcards.
*\\cmd.exeis slow. Uselike(Image, "%\\cmd.exe")or better, alookuptable. - Limit time ranges. Start with 24 hours, expand only if needed.
- Build macros. Wrap your best hunts in Splunk macros for reuse:
# Define macro: detect_lolbins(host)
# Usage:
| `detect_lolbins("workstation01")`
From Hunt to Detection
When a hunt query finds real threats, convert it to a Correlation Search in Splunk Enterprise Security. Add Risk-Based Alerting to accumulate risk scores per entity instead of firing individual alerts, which dramatically reduces alert fatigue.
Successful hunt → Save as Report → Convert to Correlation Search
→ Assign risk score → Threshold triggers Notable Event
Originally published at MalwareIntel. MalwareIntel is a free threat intelligence platform monitoring 13 public CTI sources.
Free resources:
- Defense Kit — Sigma + YARA + IOCs per malware family
- Detection Gap Analyzer — Check your MITRE ATT&CK coverage
- CTI Assessment — Rate your threat intel maturity in 5 min
Top comments (0)