Splunk Search Processing Language (SPL) Cheat Sheet: Top 30 Commands for Hunters
Every junior SOC analyst quickly learns how to type index=windows "mimikatz" into the Splunk search bar, click Search, and wait five painful minutes while the progress bar slowly crawls across 100 million events, maxing out disk I/O on the search head.
Senior threat hunters don't search like that.
In modern enterprise security operations centers ingesting 10 to 50 terabytes of telemetry a day, poorly written SPL searches crash search heads, breach SLA response times, and leave malicious adversary beacons hidden in the noise.
Splunk's Search Processing Language (SPL) is not a simple keyword search engine. It is a Unix-inspired data streaming pipeline that parses, shapes, aggregates, and correlates structured and unstructured events in real time. When you command the pipeline mechanics—distributable streaming commands, time-boundary scoping, index-time field keys, and tstats data model acceleration—your queries run 100 times faster while extracting high-fidelity detection signals.
Here is the battle-tested, zero-fluff SPL reference used by senior threat hunters to detect lateral movement, privilege escalation, credential dumping, and C2 beaconing.
1. The Anatomy of an SPL Pipeline: Why Your Queries Crawl
To write lightning-fast SPL, you must understand how Splunk executes queries across its distributed architecture.
+-----------------------------------------------------------------------------------+
| 1. Search Head | Parses SPL, checks syntax, divides work into map/reduce |
+----------------------+------------------------------------------------------------+
| 2. Indexers | Scan TSIDX bloom filters, pull raw event slices from disk |
| (Map Phase) | Filters: index, sourcetype, source, host, _time, keywords |
| | Executes distributable streaming commands (eval, rex, etc.)|
+----------------------+------------------------------------------------------------+
| 3. Pipeline Stream | Streams intermediate results over TCP back to Search Head |
+----------------------+------------------------------------------------------------+
| 4. Search Head | Executes transforming / stateful commands (stats, sort) |
| (Reduce Phase) | Formats final visualization table or alert dispatch |
+-----------------------------------------------------------------------------------+
The Three Cardinal Rules of High-Speed SPL:
Scope the Index, Sourcetype, and Time First : Always start with index=<name> sourcetype=<type> earliest=-24h. Never run an unbounded search without specifying an index.
Push Filtering Down to the Indexer : Place distributable streaming commands (search, where, fields, rex) before transforming commands (stats, chart).
Trim Fields Immediately : Raw Windows Event Logs contain over 80 extracted fields. Use | fields + _time, Computer, EventCode, Image, CommandLine early to prevent gigabytes of memory allocation on the search head.
2. The Top 30 SPL Commands for Threat Hunters
Category A: Filtering, Scoping & Ingestion
1. search
The base command that retrieves events matching terms, boolean expressions, and wildcards.
index=windows sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 Image="*\\powershell.exe"
2. where
Performs case-sensitive evaluations, mathematical comparisons, and field-to-field comparisons that search cannot do.
| where len(CommandLine) > 500 AND lower(parent_process) != "explorer.exe"
3. fields
Keeps (+) or removes (-) specific fields from the search pipeline to optimize memory usage.
| fields + _time, host, user, EventCode, TargetUserName, IpAddress
4. dedup
Removes duplicate consecutive or historical events based on specified fields. Essential for cleaning repetitive noise.
| dedup host, Image, CommandLine sortby -_time
5. head / tail
Returns the first N (head) or last N (tail) events. Use head during query prototyping to verify parsing before scanning millions of events.
| head 20
Category B: Aggregation, Statistics & Mathematical Profiling
6. stats
The workhorse transforming command. Calculates aggregate statistics across specified dimensions.
| stats count, dc(dest_ip) as distinct_destinations, values(dest_port) as targeted_ports by src_ip
Common statistical functions : count, distinct_count (dc), sum, avg, min, max, values (unique list), list (all entries).
7. eventstats
Generates summary statistics like stats, but preserves all original raw events , appending the calculated aggregate as a new field to every row.
| eventstats count as total_logins_for_user by user
| where total_logins_for_user > 100
8. streamstats
Calculates running cumulative totals, moving averages, and time differences as events stream through the pipeline . Invaluable for beaconing and delta analysis!
| sort 0 _time
| streamstats current=true window=2 range(_time) as time_delta by src_ip, dest_ip
9. timechart
Creates a time-series aggregation table formatted specifically for charting trends and visualizing spikes.
| timechart span=1h count by EventCode
10. chart
Cross-tabulates statistics across two distinct categorical fields (e.g. users across workstations).
| chart count over user by WorkstationName
11. eval
Creates new calculated fields, evaluates conditional logic, and transforms data types.
| eval risk_score = if(EventCode==4625, 20, 5), process_name = lower(Image)
Key functions : if(X, Y, Z), case(C1, V1, C2, V2, ...), coalesce(A, B, C), now(), strftime(), round().
12. rex
Extracts fields at search-time using PCRE regular expressions.
| rex field=CommandLine "(?<download_url>https?://[^\s\"']+)"
13. spath
Extracts fields from structured JSON, XML, or cloud audit payloads (AWS CloudTrail, Google Cloud Audit, Okta).
| spath input=raw_json path=actor.alternateId output=actor_user
14. lookup
Enriches streaming telemetry with static or dynamic CSV lookup tables (e.g. Asset CMDB, GeoIP, or Known Bad IOC lists).
| lookup threat_intel_ip_list.csv ip as dest_ip OUTPUT threat_category, confidence_score
Reads directly from or writes search pipeline results into a Splunk lookup table or KV store.
| inputlookup admin_accounts.csv
Category D: Correlation, Grouping & Multi-Source Analysis
16. transaction
Groups events that belong to a single conceptual transaction based on common identifiers, time constraints (maxspan), and pauses (maxpause).
| transaction src_ip maxspan=15m maxpause=2m startswith="login" endswith="logout"
(Warning: Use stats instead of transaction whenever possible for 10x better performance).
17. join
Combines results from a main search and a subsearch based on a shared key.
| join type=inner host [ search index=edr sourcetype=alerts severity=critical | fields host ]
18. append / appendcols
Appends the rows (append) or columns (appendcols) of a subsearch to the current search results.
| append [ search index=firewall action=blocked | stats count as fw_blocks by src_ip ]
19. coalesce
Returns the first non-null value among a list of fields. Perfect for normalizing usernames across different sourcetypes!
| eval unified_user = coalesce(TargetUserName, user, suser, account)
20. map
Loops a subsearch over each individual result row from the parent search. Useful for targeted IP investigations.
| map search="search index=proxy src_ip=\"$src_ip$\" earliest=-1h" maxsearches=10
21. tstats
Queries pre-built TSIDX index files and accelerated Data Models directly, completely bypassing raw event disk reads. Runs up to 1,000x faster than normal searches!
| tstats summariesonly=t count from datamodel=Endpoint.Processes where Processes.process_name="vssadmin.exe" by Processes.dest, Processes.user
Instantly extracts metadata summaries (hosts, sources, sourcetypes) directly from index bucket headers.
| metadata type=sourcetypes index=windows | eval last_seen=strftime(recentTime, "%Y-%m-%d %H:%M:%S") | table sourcetype, totalCount, last_seen
23. loadjob
Loads the cached search artifacts of a previously scheduled or saved search without re-running the heavy query.
| loadjob savedsearch="admin:ThreatIntelDailyFeed"
24. makeresults
Generates synthetic in-memory test events. Essential for developing and validating regular expressions or eval logic without touching disk indexes.
| makeresults | eval test_string="C:\\Windows\\System32\\cmd.exe /c whoami" | rex field=test_string "(?<cmd>\w+\.exe)"
25. fillnull
Replaces null or missing values with a designated default string or numerical zero.
| fillnull value="UNKNOWN" user, dest_ip
26. rare
Displays the least common values of a field. One of the single best commands for finding malicious anomalies hidden in millions of logs!
| rare limit=10 Image by host
27. top
Displays the most frequent values of a field along with count and percentage.
| top limit=10 dest_port by src_ip
28. anomalies
Computes an unexpectedness score for events based on recurring token frequency patterns.
| anomalies by host
29. cluster
Groups similar unstructured log messages using string clustering algorithms, allowing hunters to filter out repetitive baseline noise.
| cluster field=CommandLine showcount=t | sort - count
30. table & sort
Formats the final clean triage view with selected columns, sorted chronologically or by risk severity.
| table _time, host, user, Image, CommandLine | sort - _time
3. Top 5 Production Threat Hunting Templates (Ready to Run)
Copy, paste, and deploy these production hunting queries in your environment:
Hunt 1: Lateral Movement via Remote Service Creation (PsExec)
Detects adversaries creating remote services to execute commands across the domain (Windows Event 7045 & Sysmon Event 13):
index=windows sourcetype="WinEventLog:System" EventCode=7045
| rex field=ImagePath "(?<service_binary>[^\s\"]+|(?<=\")[^\"]+)"
| eval service_binary=lower(service_binary)
| search service_binary IN ("*\\psexesvc.exe*", "*\\paexec.exe*", "*\\remcom.exe*", "*\\admin$*", "*\\c$*")
| stats count earliest(_time) as FirstSeen latest(_time) as LastSeen values(ServiceName) as Services by Computer, ServiceFileName, service_binary
| eval FirstSeen=strftime(FirstSeen, "%Y-%m-%d %H:%M:%S"), LastSeen=strftime(LastSeen, "%Y-%m-%d %H:%M:%S")
| sort - count
Hunt 2: Password Spraying Burst Detection
Detects single hosts generating failed logons across multiple distinct domain accounts within a 15-minute window:
index=windows sourcetype="WinEventLog:Security" EventCode=4625
| eval TargetUserName=lower(TargetUserName)
| search NOT TargetUserName IN ("*
CyberChampion — Cybersecurity Certification Training & Hands-On Labs
Master CompTIA Security+ (SY0-701), SOC Analyst workflows, Splunk hunting, and live cloud labs.
Splunk Search Processing Language (SPL) Cheat Sheet: Top 30 Commands for Hunters
High-speed query pipes, regex field extraction, and hunting templates for Windows Event Logs and Sysmon.
By Cyber Champion Founder
quot;, "anonymous logon")
| stats count as failed_attempts, dc(TargetUserName) as distinct_targeted_users, values(TargetUserName) as user_list by IpAddress, WorkstationName
| where distinct_targeted_users >= 10 AND failed_attempts >= 20
| sort - distinct_targeted_users
Hunt 3: C2 Beaconing Detection via Jitter Delta Analysis
Calculates the standard deviation of outbound network connection intervals to detect automated malware beaconing:
index=proxy sourcetype="cisco:squid" action=allowed
| fields _time, src_ip, dest_ip, url
| sort 0 src_ip, dest_ip, _time
| streamstats current=true window=2 range(_time) as interval by src_ip, dest_ip
| where interval > 0
| stats count, avg(interval) as avg_interval, stdev(interval) as jitter_stdev by src_ip, dest_ip
| where count >= 50 AND jitter_stdev < 5
| eval avg_interval=round(avg_interval, 2), jitter_stdev=round(jitter_stdev, 2)
| sort jitter_stdev
💡 Adversary Tradecraft : If jitter_stdev is near 0, the connection is ticking on a rigid, hardcoded clock (e.g. exactly every 60.0s), confirming automated machine beaconing!
Hunt 4: Living-Off-the-Land Binaries (LOLBins) Spawning Outbound Network Connections
Detects native Windows administrative utilities making direct external network connections (Sysmon Event ID 3):
index=windows sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3
| eval ProcessName=lower(mvdedup(split(Image, "\\"))[-1])
| search ProcessName IN (
"certutil.exe", "mshta.exe", "rundll32.exe", "regsvr32.exe",
"bitsadmin.exe", "wmic.exe", "cscript.exe", "wscript.exe", "powershell.exe"
)
| search NOT (DestinationIp IN ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.1"))
| stats count earliest(_time) as FirstSeen values(CommandLine) as CmdLines by Computer, User, ProcessName, DestinationIp, DestinationPort
| eval FirstSeen=strftime(FirstSeen, "%Y-%m-%d %H:%M:%S")
| sort - count
Hunt 5: PowerShell Script Block Deobfuscation (Event 4104)
Surfaces suspicious script blocks containing download cradles or memory reflection:
index=windows sourcetype="WinEventLog:Microsoft-Windows-PowerShell/Operational" EventCode=4104
| eval ScriptData=lower(ScriptBlockText)
| search ScriptData="*downloadstring*" OR ScriptData="*invokewebrequest*" OR ScriptData="*bypass*" OR ScriptData="*system.reflection*" OR ScriptData="*frombase64string*"
| stats count earliest(_time) as FirstSeen values(ScriptBlockText) as SampleCode by Computer, UserId
| eval FirstSeen=strftime(FirstSeen, "%Y-%m-%d %H:%M:%S")
Keep this four-point mental checklist whenever drafting SPL:
Never use leading wildcards on search terms : Image="powershell.exe" forces Splunk to bypass index tokens and scan the entire string. Use Image="\\powershell.exe" or extract the binary name via rex after initial filtering.
Replace transaction with stats : transaction keeps raw events in memory and single-threads on the search head. stats distributes the computation across indexers.
Use Data Model Acceleration : For large fleets, build searches against the Endpoint and Network_Traffic data models using | tstats summariesonly=t.
Leverage Summary Indexing : If you need to generate 30-day compliance metrics, don't query 30 days of raw data every morning. Schedule an hourly summary query that writes pre-aggregated counts into a lightweight summary index.
Command the search pipeline, eliminate the noise, and hunt with surgical speed.