Tools
T1654Splunk
What Splunk is, how analysts use SPL to search and correlate security data, and how to build detection alerts and dashboards that produce signal, not noise.
View on Graph
What Splunk Is and Why Analysts Live In It
Splunk is a platform for searching, monitoring, and analyzing machine-generated data — logs, metrics, events, and security telemetry — in real time. Along with Elastic Security, it is one of the two dominant SIEM platforms. At its core, Splunk ingests data from any source (Windows Event Logs via Sysmon, syslog, network devices, cloud APIs, custom applications), indexes it for fast retrieval, and provides a search language (SPL) to query, correlate, and visualize that data.
MITRE ATT&CK maps log enumeration to T1654 (Log Enumeration) — attackers query SIEMs and log aggregators to understand what is being monitored, and Splunk is the most commonly targeted SIEM.
Core SPL Commands — The 80/20
You will use these commands in every single search. Master these and you can answer any investigation question.
| Command | What It Does | Example |
|---|---|---|
search | Filter events by field/value | search sourcetype=WinEventLog:Security EventCode=4625 (similar to KQL’s where operator) |
index | Restrict search to a data source | index=windows or index=network |
sourcetype | Filter by log type | sourcetype=WinEventLog:Security |
stats | Calculate aggregate statistics | stats count by src_ip |
table | Display specific fields in a table | table _time, src_ip, dest_ip, user |
top | Show most common values | top limit=10 src_ip |
rare | Show least common values | rare src_ip |
sort | Sort results | sort - count (descending) |
where | Filter on calculated values | where count > 100 |
eval | Create calculated fields | eval total_bytes = bytes_in + bytes_out |
timechart | Time-based chart | timechart count by src_ip |
dedup | Remove duplicate events | dedup src_ip (first occurrence per IP) |
rex | Extract data using regex | rex field=raw "(?<ip>\d+\.\d+\.\d+\.\d+)" |
transaction | Group related events into sessions | transaction src_ip maxspan=5m |
Authentication Investigations
1. Failed Logins — Top Sources
Find the source IPs attempting the most failed logins. This is your first query for any brute force investigation.
index=windows EventCode=4625
| stats count by src_ip, Account_Name
| sort - count
| head 20
What it tells you: The top 20 IPs with the most failed login attempts, broken down by targeted account.
2. Brute Force — IPs Crossing Threshold
Identify IPs that have exceeded a threshold of failed logins within a time window.
index=windows EventCode=4625
| bucket _time span=5m
| stats count by src_ip, _time
| where count > 10
| sort - count
What it tells you: IPs that attempted > 10 failed logins within a 5-minute window — classic brute force pattern.
3. Password Spray Detection — One Account, Many Users
Password spray attacks try the same password against many accounts. Detect this by looking for failed logins with the same destination account but from multiple source IPs.
index=windows EventCode=4625
| stats dc(src_ip) as unique_ips, count by Account_Name
| where unique_ips > 5
| sort - unique_ips
What it tells you: Accounts that received failed logins from > 5 different IPs — password spray victims.
4. Successful Logins After Brute Force
Find IPs that transitioned from failed to successful authentication — the attacker found the right password.
index=windows (EventCode=4625 OR EventCode=4624)
| eval auth_type = if(EventCode=4625, "failed", "success")
| stats earliest(_time) as first, latest(_time) as last, values(auth_type) as types by src_ip, Account_Name
| where match(types, "failed") AND match(types, "success")
| eval window = round((last - first)/60)
| table src_ip, Account_Name, first, last, window
What it tells you: IPs that had both failed and successful logins for the same account — the attacker brute-forced and succeeded.
Process and Command-Line Investigations
5. Suspicious PowerShell
Find encoded PowerShell commands — a near-universal malware indicator.
index=windows EventCode=4688
| search CommandLine IN ("*powershell*", "*pwsh*")
| search CommandLine IN ("*EncodedCommand*", "*-enc*", "*iex*", "*DownloadString*", "*Invoke-Expression*")
| stats count by ComputerName, UserName, CommandLine, ParentProcessName
| sort - count
What it tells you: Every host that ran PowerShell with suspicious parameters — the most reliable detection for initial access and lateral movement.
6. LOLBin Execution (Living Off the Land)
Find execution of binaries that attackers commonly abuse for evasion.
index=windows EventCode=4688
| search NewProcessName IN ("*\\rundll32.exe", "*\\regsvr32.exe", "*\\mshta.exe", "*\\cscript.exe", "*\\wscript.exe", "*\\certutil.exe", "*\\bitsadmin.exe", "*\\msbuild.exe", "*\\installutil.exe")
| stats count by ComputerName, NewProcessName, CommandLine, ParentProcessName
| sort - count
What it tells you: Which hosts are running LOLBins and what their parent processes are — distinguish automation scripts from malicious usage.
7. Process Tree — Unusual Parent-Child
Find processes whose parent is unexpected (e.g., winword.exe spawning cmd.exe, which indicates a macro running).
index=windows EventCode=4688
| eval suspicious = case(
match(ParentProcessName, "WINWORD.EXE"), "Office->Process",
match(ParentProcessName, "EXCEL.EXE"), "Office->Process",
match(ParentProcessName, "OUTLOOK.EXE"), "Office->Process",
match(ParentProcessName, "chrome.exe"), "Browser->Process",
match(ParentProcessName, "firefox.exe"), "Browser->Process",
1=1, "Normal"
)
| search suspicious != Normal
| stats count by ParentProcessName, NewProcessName, ComputerName
| sort - count
What it tells you: All instances of Office or browser applications spawning child executables — the classic macro malware and drive-by download pattern.
Network and C2 Investigations
8. Outbound Connections — Volume by Host
Find hosts making the most outbound connections — potential C2 candidates.
index=network sourcetype=netflow
| stats sum(bytes) as total_bytes, dc(dest_ip) as unique_destinations, count as connections by src_ip
| where connections > 1000
| sort - total_bytes
What it tells you: Hosts with high outbound connection counts and bytes transferred — volume anomaly detection.
9. Beaconing Detection — Regular Connection Intervals
Beaconing malware connects to C2 at regular intervals. Find connections with consistent inter-arrival times.
index=proxy dest_ip="*"
| stats earliest(_time) as first, latest(_time) as last, count as total_connections by src_ip, dest_ip
| eval duration_hours = (last - first) / 3600
| eval connections_per_hour = total_connections / duration_hours
| where connections_per_hour > 1 AND connections_per_hour < 60
| eval interval_seconds = 3600 / connections_per_hour
| table src_ip, dest_ip, total_connections, duration_hours, interval_seconds
| sort interval_seconds
What it tells you: Pairs of src_ip→dest_ip with regular connection patterns — not too frequent (not web browsing), not too rare (not admin tasks). The interval in seconds should be roughly consistent for true beacons.
10. DNS — High TXT Query Volume (Tunneling)
Find hosts making an unusual number of TXT DNS queries, which have the highest DNS tunneling payload capacity.
index=dns query_type=TXT
| stats count by src_ip, query
| where count > 20
| sort - count
What it tells you: Hosts making many TXT queries — potential DNS tunneling for C2 or exfiltration.
11. DNS — Long Subdomain Detection
DNS tunneling encodes data in subdomains. Find unusually long subdomain names.
index=dns
| eval subdomain_length = len(split(query, ".")[0])
| where subdomain_length > 30
| stats count by src_ip, query, query_type
| sort - count
What it tells you: Hosts querying domains with subdomains longer than 30 characters — strong DNS tunneling indicator.
12. Connections to Newly Observed Domains
If you have a feed of domain registration dates, cross-reference connection data.
index=proxy
| lookup domain_age_lookup domain as dest_host output first_seen
| where first_seen > relative_time(now(), "-30d")
| stats count by src_ip, dest_host, first_seen
| sort - count
What it tells you: Connections to domains first seen < 30 days ago — phishing and C2 domains are almost always new.
Data Exfiltration Detection
13. Large Outbound Transfers
Find the top data exporters by volume.
index=network sourcetype=flow
| stats sum(bytes) as total_bytes, sum(packets) as total_packets by src_ip, dest_ip, dest_port
| where total_bytes > 100000000 # > 100MB
| eval total_mb = round(total_bytes/1048576, 2)
| table src_ip, dest_ip, dest_port, total_mb, total_packets
| sort - total_mb
What it tells you: Hosts that transferred > 100MB to external destinations — investigate for data exfiltration.
14. After-Hours Data Transfer
Same as above, but filtered to after-hours time windows.
index=network sourcetype=flow
| eval hour = strftime(_time, "%H")
| where hour >= 18 OR hour <= 6
| stats sum(bytes) as total_bytes by src_ip, dest_ip, dest_port, user
| where total_bytes > 50000000
| eval total_mb = round(total_bytes/1048576, 2)
| sort - total_mb
What it tells you: Data transfers between 6 PM and 6 AM — exfiltration is most common after hours.
Privilege Escalation and Lateral Movement
15. Admin Login Tracking
Find all interactive logons with admin privileges.
index=windows EventCode=4672
| stats count by Account_Name, ComputerName, src_ip
| sort - count
What it tells you: Every time a user received elevated privileges on a system — track admin account usage.
16. Lateral Movement — Explicit Credential Use (4648)
Event 4648 fires when explicit credentials are used (RunAs, scheduled tasks, WMI, PsExec).
index=windows EventCode=4648
| stats count by Account_Name, Target_Server, ProcessName
| sort - count
What it tells you: All instances of explicit credential use — lateral movement via WMI, PsExec, or scheduled tasks.
17. New Scheduled Task Creation
Attackers persist via scheduled tasks. Find recently created tasks.
index=windows EventCode=4698
| table _time, ComputerName, TaskName, UserName, Command
| sort - _time
What it tells you: Every new scheduled task — compare against a baseline of legitimate tasks.
Threat Hunting Queries
18. Unusual Service Installations
index=windows EventCode=7045
| search ServiceName != "TrustedInstaller" AND ServiceType = "own process"
| stats count by ServiceName, ImagePath, ComputerName
| sort - _time
19. Accounts Created Outside IT Processes
index=windows EventCode=4720
| table _time, Account_Name, ComputerName
| sort - _time
20. Domain Admin Group Modifications
index=windows EventCode IN (4728, 4732, 4746, 4751, 4756)
| table _time, Account_Name, TargetUserName, ComputerName
| sort - _time
Building Detections — From Search to Alert
Once you’ve found a useful query, turn it into a real-time alert:
- Set a schedule. Most queries should run every 5-15 minutes looking back 15-60 minutes.
- Set a threshold. “Alert if count > 5 from a single source IP in 15 minutes” prevents noise from single events.
- Add context. The alert message should include: what was detected, affected assets, severity, and a link to the runbook.
- Tier the alert. Low → inform, review next shift. Medium → create incident ticket. High → page on-call analyst.
Alert Example
Name: Brute Force - Password Spray Detection
Search: index=windows EventCode=4625
| stats dc(Account_Name) as unique_accounts, count by src_ip
| where unique_accounts > 10
Schedule: Every 5 minutes, look back 30 minutes
Threshold: count > 50
Severity: Medium - High (based on unique_accounts count)
Response: Review source IP, check for 4624 (successful login), block at firewall if malicious.
Related
- Azure Sentinel — detection and response for T1654 techniques
- CyberChef — detection and response for T1654 techniques
- Log Sources Overview — covers the log sources overview concepts
