SIEM Fundamentals with Splunk: Searching & SPL
This is Chapter 4 of the SOC & Blue Team notebook, and it is the first chapter where you put a real tool under your hands. The last three chapters built the frame: the SOC and its workflow (Chapter 1), the data sources (Chapter 2), and the technique-level thinking of ATT&CK (Chapter 3). Now we make it concrete. Splunk is one of the most widely deployed SIEMs in the world, and its search language, SPL (Search Processing Language), is a skill that appears in job descriptions across the industry. By the end of this chapter you will be able to install Splunk, get data in, and write SPL that turns the raw logs of Chapter 2 into the technique-level detections of Chapter 3.
This is a tool primer, so we teach Splunk from zero — what it is, why it exists, how it is architected, and how to think in SPL — before writing a single detection. The goal is not to memorize commands but to build a mental model: SPL is a pipeline language, a series of commands connected by pipes, each transforming the data flowing through it, exactly like the grep | awk | sort | uniq pipelines from Chapter 2, but vastly more powerful. Once that model clicks, the dozens of commands become variations on a theme.
A framing note consistent with the notebook: everything here operates on data your organization is authorized to collect and analyze. Splunk is a monitoring tool for systems you own; the SPL you learn is for detecting attackers against your own environment. Use the free lab data and your own home lab (Chapter 1, Part 13f) to practice — never point analysis tooling at data you have no authority over.
We build from what a SIEM is and Splunk’s place in the market, through architecture, getting data in, the anatomy of a search, the core SPL commands in depth, fields and extraction, statistical and transforming commands, lookups and enrichment, correlation across events, saved searches and alerting, dashboards, a full hands-on detection lab, performance and pitfalls, a consolidated detection-and-defense section, a final revision, a cheat sheet, and topic-specific practice.
Why This Matters
Section titled “Why This Matters”SPL is where the abstract becomes employable. A hiring manager for a SOC role will not ask you to define “defense in depth” for long; they will put a dataset in front of you and ask you to find the attacker. That means writing searches: “show me failed logins grouped by source, then the ones that eventually succeeded,” “find PowerShell with encoded commands,” “surface hosts beaconing at regular intervals to rare domains.” Every one of those is SPL, and every one maps back to an ATT&CK technique from Chapter 3. The language is the bridge between knowing what an attack looks like and actually catching it in your data.
Beyond hiring, SPL is the daily instrument of triage and hunting. When an alert fires, you pivot in SPL to answer “what happened next?” — the exact skill Chapter 1’s lab demanded. When you threat-hunt, you form a hypothesis (Chapter 3) and express it as a search. When you build a detection, you write it in SPL and schedule it as an alert. The SIEM is the SOC’s central nervous system (Chapter 1), and SPL is how you talk to it.
Who this is for: aspiring SOC analysts and detection engineers who will live in a SIEM, and anyone who wants a transferable, marketable skill. SPL concepts (pipeline search, stats, field extraction) also transfer conceptually to KQL (Chapter 5) and Elastic (Chapter 6), so this chapter does double duty as a foundation for all SIEM query languages.
Part 1: What a SIEM Is (Recap and Deepening)
Section titled “Part 1: What a SIEM Is (Recap and Deepening)”Chapter 1 introduced the SIEM as the SOC’s central nervous system: it aggregates logs from across the environment, normalizes them, correlates them, and runs detection rules that generate alerts. Let us deepen that now that you have the data-source and technique context.
A SIEM does five things:
- Collect — ingest logs and telemetry from every source (Chapter 2’s layers).
- Normalize/parse — turn heterogeneous formats into searchable fields (Chapter 2’s schemas).
- Store/index — keep the data searchable, usually with time as the primary axis.
- Detect/correlate — run rules and correlations that fire alerts, mapped to techniques (Chapter 3).
- Investigate — let analysts search and pivot interactively during triage and hunting.
Splunk is a SIEM (and more — it started as a general log-analytics platform and is used well beyond security). Its defining trait is that it indexes data in a schema-flexible way and gives you a powerful search language to ask arbitrary questions of it. Where a traditional database needs you to define a schema before loading data, Splunk applies schema-on-read: it stores raw events and extracts fields at search time, which is what makes it so flexible for the messy, varied logs a SOC deals with.
The Splunk market context, briefly: Splunk pioneered searchable machine data and remains a market leader, especially in large enterprises; Splunk Enterprise Security (ES) is its dedicated SIEM app layering correlation searches, risk-based alerting, and dashboards on top of core Splunk. Competitors include Microsoft Sentinel (Chapter 5) and Elastic (Chapter 6). Learning Splunk gives you both a specific in-demand skill and the conceptual grounding for the others.
Part 2: Splunk Architecture From Scratch
Section titled “Part 2: Splunk Architecture From Scratch”Before searching, understand what you are searching. Splunk’s architecture has three core roles. In a small deployment they can live on one machine; at enterprise scale they are separate, clustered tiers.
flowchart LR
subgraph Sources
S1[Endpoints]
S2[Servers]
S3[Network / Cloud]
end
S1 --> UF[Universal Forwarder]
S2 --> UF
S3 --> HF[Heavy Forwarder / HEC / API]
UF --> IDX[Indexers - store + index]
HF --> IDX
IDX --> SH[Search Head - run SPL]
SH --> USER[Analyst]
- Forwarders — lightweight agents installed on sources that collect and ship data to indexers. The Universal Forwarder (UF) is a small, efficient agent that just forwards (the workhorse). A Heavy Forwarder (HF) can parse and route before forwarding. This is Chapter 2’s “collection/forwarding” stage in Splunk terms.
- Indexers — receive data, parse it into events, and write it to disk in time-bucketed indexes. They do the storage and the heavy lifting at search time. Data lands in an index (think of it as a named container, e.g.,
main,wineventlog,firewall). - Search Heads — where analysts run SPL. The search head distributes a search to the indexers, which each search their slice of data and return results to be merged. In a cluster, multiple search heads share saved searches and dashboards.
Two supporting concepts you must know:
- HTTP Event Collector (HEC) — an endpoint for sending data to Splunk over HTTP(S), common for cloud and application logs (agentless ingestion).
- Deployment server — centrally manages forwarder configurations at scale.
When data arrives, Splunk assigns every event three default metadata fields that you will use constantly:
index— which index it landed in.sourcetype— the kind of data (e.g.,WinEventLog:Security,access_combined,cisco:asa). This drives field extraction — the single most important field for a searcher, because it tells Splunk (and you) how to interpret the event.source— the origin (file path, input).host— the machine that produced it.
Understanding this pipeline answers real questions: “why is my data missing?” (forwarder down, wrong index, parsing error) maps directly onto Chapter 2’s pipeline-health lesson.
Part 3: Getting Data In (and Trying It Yourself)
Section titled “Part 3: Getting Data In (and Trying It Yourself)”You learn SPL fastest with data to search. The free options:
- Splunk Free / Splunk Enterprise trial — install locally; the trial is full-featured for 60 days, then Splunk Free continues with a daily ingest limit, fine for a lab.
- Splunk Cloud trial — hosted.
- Boss of the SOC (BOTS) datasets — free, realistic security datasets built for exactly this kind of practice; the gold standard for learning SPL against real attack scenarios.
A minimal local install and data load (Linux):
# Download and install Splunk Enterprise (trial)wget -O splunk.tgz 'https://download.splunk.com/.../splunk-<ver>-Linux-x86_64.tgz'tar xzf splunk.tgz -C /opt/opt/splunk/bin/splunk start --accept-license # set admin password on first run/opt/splunk/bin/splunk enable boot-start
# Web UI now at http://localhost:8000# Add data: Settings > Add Data > Upload -> pick a log file, set sourcetype, indexTo ingest from a host, install a Universal Forwarder and point it at your indexer:
# On the source host, after installing the UF:/opt/splunkforwarder/bin/splunk add forward-server INDEXER_IP:9997/opt/splunkforwarder/bin/splunk add monitor /var/log/ # monitor a path/opt/splunkforwarder/bin/splunk restartOnce data is in, everything else is SPL. The rest of this chapter assumes you have some data — ideally BOTS, or your own home-lab telemetry from Chapter 1 (Sysmon, Windows events, firewall).
Part 4: The Anatomy of a Search
Section titled “Part 4: The Anatomy of a Search”Open the Search & Reporting app and you get a search bar and a time picker. The time picker is not optional — Splunk is time-series-first, and choosing “Last 24 hours” vs “All time” is the difference between a fast search and one that scans everything. Always scope time.
A Splunk search is a pipeline: a base search that retrieves events, followed by a series of commands separated by pipes (|), each transforming the result set.
index=wineventlog sourcetype=WinEventLog:Security EventCode=4625| stats count by src_ip, user| sort - countRead it as a sentence: from the Windows security index, get failed-logon events (4625), count them by source IP and user, and sort by count descending. Each stage feeds the next, left to right. That is the whole mental model — retrieve, then transform, then transform again.
The base search (before the first pipe) is where you should be as specific as possible, because it determines how much data Splunk has to read. index=, sourcetype=, and time are your best friends; the more you constrain here, the faster everything downstream. Everything after a pipe is a search command.
flowchart LR
A["Base search: index/sourcetype/time + keywords"] --> B["| command 1 (e.g. stats)"]
B --> C["| command 2 (e.g. eval)"]
C --> D["| command 3 (e.g. sort)"]
D --> E[Results table / viz]
Splunk commands fall into rough families you will come to recognize:
- Streaming — act on each event as it flows (
eval,rex,where,fields). - Transforming — turn events into a results table (
stats,chart,timechart,top,rare). - Generating — produce their own data (
tstats,inputlookup,makeresults). - Orchestrating — control flow (
append,join,map).
Part 5: Core SPL Commands, In Depth
Section titled “Part 5: Core SPL Commands, In Depth”Here is the working toolkit, each taught with a real security example. Master these ten and you can write the vast majority of detections.
search — filter events
Section titled “search — filter events”The implicit first command. You can also use search after a pipe to filter further. Keywords, field comparisons, booleans, and wildcards all work:
index=firewall action=blocked dest_port=3389 # blocked RDP attemptsindex=web sourcetype=access_combined status>=400 # HTTP errorsindex=main ("powershell" AND "-enc") NOT user=svc_backup # boolean + exclusionindex=main user=a* dest_ip=10.0.* # wildcardsTip: field comparisons in the base search (status>=400) are efficient; save where/eval for computed conditions.
stats — aggregate
Section titled “stats — aggregate”The single most important transforming command. It computes aggregate statistics grouped by fields.
index=wineventlog EventCode=4625| stats count AS failures, dc(user) AS distinct_users, values(user) AS users_tried, earliest(_time) AS first, latest(_time) AS last by src_ip| sort - failuresCommon stats functions: count, dc() (distinct count), sum(), avg(), min(), max(), values() (unique list), list() (all values), earliest()/latest(). dc(user) from one source IP is how you spot password spraying (one source, many users) versus brute force (one source, one user, high count) — the Chapter 2 distinction, now expressed in SPL.
eval — compute new fields
Section titled “eval — compute new fields”eval creates or modifies fields with expressions — arithmetic, string functions, conditionals.
index=firewall| eval MB_out = round(bytes_out/1024/1024, 2)| eval risk = if(MB_out > 500 AND dest_category="cloud_storage", "high", "normal")| where risk="high"eval supports if(), case(), coalesce(), strftime()/strptime() (time formatting), upper()/lower(), len(), mvcount(), and much more. It is your Swiss-army knife for shaping data.
where — filter on computed conditions
Section titled “where — filter on computed conditions”Like search but evaluates expressions and compares fields to fields:
index=proxy| stats sum(bytes_out) AS total by src_ip, dest| where total > 1073741824 # > 1 GB exfil candidaterex — extract fields with regex
Section titled “rex — extract fields with regex”When a field is not already parsed, rex pulls it out at search time using named capture groups:
index=main sourcetype=linux_secure "Failed password"| rex "from (?<src_ip>\d+\.\d+\.\d+\.\d+) port (?<src_port>\d+)"| stats count by src_ipThis is Chapter 2’s “parsing” done ad hoc in the search — invaluable when a source is not fully fielded.
top / rare — quick frequency analysis
Section titled “top / rare — quick frequency analysis”index=dns | top limit=20 query # most-queried domainsindex=dns | rare limit=20 query # rarest domains (often malicious!)rare is a hunting favorite: the rarest DNS queries or user agents are often the malicious ones — an idea straight from Chapter 3’s “rare destination” C2 detection.
timechart — trends over time
Section titled “timechart — trends over time”index=firewall action=blocked| timechart span=1h count by dest_porttimechart buckets by time and is how you visualize beaconing, spikes, and diurnal patterns. Regular, evenly-spaced peaks from one host = possible C2 beacon.
fields, rename, sort, head/tail — shaping output
Section titled “fields, rename, sort, head/tail — shaping output”index=wineventlog EventCode=4624| fields _time, user, src_ip, LogonType # keep only these (faster)| rename LogonType AS logon_type| sort - _time| head 100 # first 100| fields x,y early in a pipeline speeds things up by discarding data you do not need. head/tail limit rows; dedup removes duplicates.
lookup — enrich with external context
Section titled “lookup — enrich with external context”Lookups join your events to reference tables (CSV or KV store) — asset criticality, user roles, IOC lists — Chapter 1’s enrichment made real:
index=firewall| lookup threat_intel_ips ip AS dest_ip OUTPUT threat_actor, confidence| where isnotnull(threat_actor)Here a lookup table threat_intel_ips maps IPs to known actors; the search flags any destination that matches. This is how IOC matching (Chapter 2) is wired into Splunk.
transaction — group related events
Section titled “transaction — group related events”transaction stitches events sharing a field into a single grouped event, useful for sessions:
index=web sourcetype=access_combined| transaction clientip maxpause=5m| where eventcount > 100 # sessions with 100+ requestsPowerful but expensive — prefer stats where you can (see Part 11 on performance).
Part 6: Fields, Sourcetypes, and Extraction
Section titled “Part 6: Fields, Sourcetypes, and Extraction”Splunk’s power comes from fields. Some are extracted automatically (Splunk recognizes key-value pairs and JSON); others need help. Understanding extraction saves you constant frustration.
- Automatic extraction — for well-structured data (key=value, JSON), Splunk auto-fields at search time.
index=cloudtrail | table eventName, sourceIPAddressjust works because CloudTrail is JSON. - Sourcetype-based extraction — Splunk apps (Technology Add-ons / TAs) ship field extractions and CIM mappings for common sources. The Splunk Add-on for Microsoft Windows and Sysmon TA field-extract Windows/Sysmon events so
EventCode,user,src_ip,parent_process, etc. are ready to use. Install the right TA for each source — it is the difference between clean fields and raw text. - Manual extraction —
rexat search time (Part 5), or persistent field extractions defined inprops.conf/transforms.conf.
The Common Information Model (CIM) from Chapter 2 matters here: TAs map source-specific fields to CIM’s normalized names (e.g., both firewall and proxy dest→dest, user fields→user), so a single CIM-compliant search works across sources. Splunk ES and many detections are written against CIM data models via tstats for speed.
flowchart LR
RAW[Raw event] --> ST[sourcetype identifies it]
ST --> TA[Technology Add-on extracts fields]
TA --> CIM[CIM maps to normalized fields]
CIM --> SEARCH[One search works across sources]
Part 7: Building Real Detections in SPL
Section titled “Part 7: Building Real Detections in SPL”Now the payoff: turning Chapter 3’s technique recipes into working SPL. Each of these is a detection you could schedule as an alert.
Brute force and password spraying (T1110)
Section titled “Brute force and password spraying (T1110)”# Brute force: many failures for one account from one sourceindex=wineventlog EventCode=4625| stats count AS failures, values(user) AS users, dc(user) AS nusers by src_ip| where failures > 30 AND nusers <= 2
# Password spraying: one source, one/few attempts each across MANY usersindex=wineventlog EventCode=4625| bucket _time span=10m| stats dc(user) AS users_targeted, count AS attempts by src_ip, _time| where users_targeted > 10 AND attempts < users_targeted*3The two searches encode the exact distinction from Chapter 2: brute force = high count / few users; spraying = many distinct users / few attempts each.
Successful login after brute force
Section titled “Successful login after brute force”index=wineventlog (EventCode=4625 OR EventCode=4624)| stats count(eval(EventCode=4625)) AS fails, count(eval(EventCode=4624)) AS successes, latest(eval(if(EventCode=4624,_time,null()))) AS success_time by src_ip, user| where fails > 20 AND successes > 0This surfaces the dangerous pattern — a flood of failures followed by a success — the highest-signal outcome from Chapter 2’s brute-force lesson.
Office spawning a script interpreter (T1204/T1566)
Section titled “Office spawning a script interpreter (T1204/T1566)”index=sysmon EventCode=1 (ParentImage="*\\winword.exe" OR ParentImage="*\\excel.exe" OR ParentImage="*\\powerpnt.exe" OR ParentImage="*\\outlook.exe") (Image="*\\powershell.exe" OR Image="*\\cmd.exe" OR Image="*\\wscript.exe" OR Image="*\\mshta.exe")| table _time, host, User, ParentImage, Image, CommandLineDirectly implements Chapter 3’s most valuable early detection.
Encoded/suspicious PowerShell (T1059.001)
Section titled “Encoded/suspicious PowerShell (T1059.001)”index=sysmon EventCode=1 Image="*\\powershell.exe"| eval susp = case( match(CommandLine,"(?i)-enc"), "encoded", match(CommandLine,"(?i)-w\s+hidden"), "hidden", match(CommandLine,"(?i)downloadstring|iex|invoke-expression"), "download_cradle", 1==1, "other")| where susp != "other"| table _time, host, User, susp, CommandLineKerberoasting (T1558.003)
Section titled “Kerberoasting (T1558.003)”index=wineventlog EventCode=4769 Ticket_Options=0x40810000 Ticket_Encryption_Type=0x17 # RC4, weak| stats dc(Service_Name) AS services, count AS requests by Account_Name, src_ip| where services > 5Uses data (4769) you likely already collect — Chapter 3’s “cheap gap to close.”
C2 beaconing (T1071.001)
Section titled “C2 beaconing (T1071.001)”index=proxy OR index=firewall| bucket _time span=1m| stats count by src_ip, dest, _time| stats avg(count) AS avg, stdev(count) AS sd, dc(_time) AS active_minutes by src_ip, dest| where active_minutes > 30 AND sd < 1 # very regular = beacon-likeRegular-interval, low-variance connections to one destination — the network signature of a beacon.
Cleared event logs (T1070.001)
Section titled “Cleared event logs (T1070.001)”index=wineventlog EventCode=1102| table _time, host, userNear-pure signal; alert on every occurrence.
Data exfiltration to cloud storage (T1567.002)
Section titled “Data exfiltration to cloud storage (T1567.002)”index=proxy dest_category="cloud_storage"| stats sum(bytes_out) AS total_bytes by src_ip, user, dest| eval GB = round(total_bytes/1024/1024/1024, 2)| where GB > 1| sort - GBEach of these carries an ATT&CK tag in its name/metadata, keeping the heatmap from Chapter 3 honest and current.
Part 8: Correlation and Risk-Based Alerting in Splunk
Section titled “Part 8: Correlation and Risk-Based Alerting in Splunk”Chapter 3 introduced risk-based alerting (RBA): accumulate per-entity risk from many technique detections and fire one high-confidence incident. Splunk ES implements this directly, but you can express the idea in core SPL. The pattern: each detection writes a “risk event” (via collect into a risk index) with a score and the entity; a correlation search sums risk per entity.
# Correlation search: entities crossing a risk threshold in 24hindex=risk earliest=-24h| stats sum(risk_score) AS total_risk, values(source) AS contributing_detections, dc(source) AS distinct_detections by risk_object| where total_risk >= 80 OR distinct_detections >= 3| sort - total_riskThis turns a dozen scattered medium alerts on one host into a single, prioritized incident — the alert-fatigue fix from Chapters 1 and 3. Splunk ES’s Risk Framework and Notable Events productize this, but understanding the SPL underneath demystifies it.
flowchart LR
D1[Detection: Office->script] --> R[(risk index)]
D2[Detection: encoded PS] --> R
D3[Detection: SMB lateral] --> R
R --> C[Correlation search: sum risk by entity]
C --> N[Notable event / incident if over threshold]
Part 9: Alerts, Saved Searches, and Dashboards
Section titled “Part 9: Alerts, Saved Searches, and Dashboards”A detection is only useful if it runs continuously and notifies someone. In Splunk:
- Saved search — a stored SPL query you can run on demand or on a schedule.
- Scheduled alert — a saved search that runs on a cron schedule and triggers an action when results meet a condition (e.g., “number of results > 0”). Actions include emailing, creating a ticket, running a script/webhook, or (in ES) creating a Notable Event.
- Real-time alert — fires as matching events arrive (powerful but resource-heavy; use sparingly).
Creating an alert from a search (conceptually): run the SPL, Save As → Alert, set the schedule (e.g., every 5 minutes over the last 5 minutes), the trigger condition (results > 0), throttling (suppress duplicate alerts for N minutes to avoid a storm), severity, and the action. Throttling and good trigger conditions are what keep a detection from becoming the alert-fatigue problem it was meant to solve.
Dashboards turn searches into visual panels — a SOC overview (top source IPs, alert volume by severity, geographic sign-in map, failed-vs-successful logon trend). Built from saved searches, dashboards give analysts situational awareness at a glance. Keep them focused: a dashboard that tries to show everything shows nothing.
# A dashboard panel: failed logons over time by sourceindex=wineventlog EventCode=4625| timechart span=1h count by src_ip limit=10Part 10: Hands-On Lab — Hunt an Intrusion in Splunk
Section titled “Part 10: Hands-On Lab — Hunt an Intrusion in Splunk”This lab reproduces the multi-source intrusion from Chapters 2–3, now as SPL you run against BOTS-style data. Follow the reasoning; adapt field names to your dataset.
Scenario. Suspicious activity reported on host WKS-2291 / user m.singh. Hunt it.
Step 1 — Scope and orient
Section titled “Step 1 — Scope and orient”index=* host=WKS-2291 earliest=-24h| stats count by sourcetypeThis shows which data sources you have for the host (Sysmon, WinEventLog, firewall, proxy). Orientation first — know your telemetry before diving in.
Step 2 — Find the initial execution
Section titled “Step 2 — Find the initial execution”index=sysmon host=WKS-2291 EventCode=1 ParentImage="*\\winword.exe"| table _time, Image, CommandLine, UserExpected: winword.exe spawning powershell.exe with an encoded command — Initial Access + Execution confirmed.
Step 3 — Decode and pivot to C2
Section titled “Step 3 — Decode and pivot to C2”index=sysmon host=WKS-2291 EventCode=3 Image="*\\powershell.exe"| table _time, DestinationIp, DestinationHostname, DestinationPortReveals the outbound connection to the attacker IP/domain. Cross-check DNS:
index=dns host=WKS-2291| stats count by query, answer| sort - countThe rare domain resolving to the C2 IP is your durable IOC.
Step 4 — Hunt the IOC across the fleet
Section titled “Step 4 — Hunt the IOC across the fleet”index=dns query="cdn-updates.xyz"| stats values(host) AS affected_hosts, count by queryDid other hosts resolve the same domain? This scopes the incident beyond patient zero — the Chapter 2 pivot.
Step 5 — Detect lateral movement and collection
Section titled “Step 5 — Detect lateral movement and collection”index=wineventlog EventCode=4624 LogonType=3 user=m.singh| stats count by src_ip, dest, ComputerNameindex=wineventlog EventCode=4663 Object_Name="*\\Finance\\*"| search Account_Name=m.singh| table _time, Object_Name, AccessesConfirms the SMB logon to the file server and the access to payroll.xlsx — Lateral Movement + Collection.
Step 6 — Assemble the timeline
Section titled “Step 6 — Assemble the timeline”index=* host=WKS-2291 OR (user=m.singh) earliest="02/12/2027:08:00:00" latest="02/12/2027:10:00:00"| eval summary=coalesce(CommandLine, Object_Name, query, signature)| table _time, sourcetype, host, user, summary| sort _timeThis unified, time-sorted view is the incident narrative — the Chapter 2 timeline skill, now one search. From here you map to ATT&CK (Chapter 3), disposition, and escalate (Chapter 1). The whole notebook so far, executed in Splunk.
Step 7 — Turn the hunt into a detection
Section titled “Step 7 — Turn the hunt into a detection”The most important step: convert what you found into a scheduled alert so it catches the next one automatically.
# Saved as an alert, every 5 min, trigger if results > 0index=sysmon EventCode=1 ParentImage="*\\winword.exe" (Image="*\\powershell.exe" OR Image="*\\cmd.exe" OR Image="*\\mshta.exe")| table _time, host, User, ParentImage, Image, CommandLineThat is the feedback loop from Chapter 1, closed in Splunk: an incident became a durable, technique-level detection.
Part 11: Performance, Pitfalls, and Good SPL Hygiene
Section titled “Part 11: Performance, Pitfalls, and Good SPL Hygiene”Bad SPL is slow SPL, and in a busy SOC slow searches cost real time. The habits that matter:
- Constrain the base search. Always specify
index=,sourcetype=, and a tight time range.index=* | search ...scans everything — the classic beginner mistake. - Filter early, transform late. Do keyword and field filtering before
stats/eval, so less data flows downstream. Use| fieldsto drop unneeded fields early. - Prefer
statsovertransactionandjoin.transactionandjoinare expensive and have limits; most correlation can be done withstats ... byandeval. Reach forjoinonly when you truly must combine differently-shaped datasets. - Use
tstatsfor scale.tstatsruns against indexed/accelerated data (tsidx, data models) and is dramatically faster for large-scale aggregation — the basis of Splunk ES detections. - Beware wildcards at the start of a term.
*evilforces a full scan;evil*can use the index. Leading wildcards are slow. - Understand search modes. “Fast” mode skips field discovery for speed; “Verbose” extracts everything (slower). Use Fast for known-field searches.
- Mind case and field names. SPL keywords are case-insensitive for search terms but field names are case-sensitive, and
EventCodevsevent_codedepends on your TA. Confirm field names with| fieldsummaryor the fields sidebar.
| Pitfall | Symptom | Fix |
|---|---|---|
index=* everywhere | Slow, expensive searches | Always name index + sourcetype |
Leading wildcard *term | Full scan | Anchor terms; use term* |
Overusing join/transaction | Timeouts, truncation | Rebuild with stats by |
| No time bound | Scans all time | Set the time picker |
| Wrong field names | Empty results | Check TA/CIM field names |
| Real-time alerts everywhere | Indexer load | Use scheduled alerts |
Part 11b: More SPL You Will Actually Use
Section titled “Part 11b: More SPL You Will Actually Use”The ten core commands cover most work, but a handful more come up constantly in real detection engineering. Each is worth knowing on sight.
streamstats and eventstats — stats without collapsing rows
Section titled “streamstats and eventstats — stats without collapsing rows”stats collapses events into a summary table; sometimes you want the aggregate added to each event. eventstats computes an aggregate across all results and adds it as a field to every row; streamstats does it cumulatively in order (great for time-series like beacon-interval analysis).
# Add each user's average logon count next to every event (eventstats)index=wineventlog EventCode=4624| eventstats avg(count) AS user_avg by user
# Time between consecutive connections per host (streamstats) - beacon huntingindex=proxy| sort 0 src_ip _time| streamstats current=f last(_time) AS prev_time by src_ip, dest| eval delta = _time - prev_time| stats avg(delta) AS avg_interval, stdev(delta) AS jitter by src_ip, dest| where avg_interval > 30 AND jitter < 5 # regular interval = beaconThat beacon search is more precise than the Part 7 version because it measures the interval between connections and its variance directly — low jitter is the hallmark of automated C2.
bin/bucket — group time (and numbers) into buckets
Section titled “bin/bucket — group time (and numbers) into buckets”index=wineventlog EventCode=4625| bin _time span=5m| stats count by _time, src_ip # failures per 5-minute windowsubsearches — feed one search into another
Section titled “subsearches — feed one search into another”A subsearch (in square brackets) runs first and returns values used by the outer search. Great for “find X, then look up everything those X did.”
# Find all activity from IPs that failed login more than 100 timesindex=wineventlog EventCode=4624 [ search index=wineventlog EventCode=4625 | stats count by src_ip | where count > 100 | fields src_ip]Subsearches are powerful but bounded (default 10k results / 60s) — keep them small; for big joins, prefer stats.
spath and JSON — parse structured logs
Section titled “spath and JSON — parse structured logs”For JSON (cloud, EDR), spath extracts nested fields:
index=cloudtrail| spath path=userIdentity.userName output=user| spath path=responseElements.ConsoleLogin output=result| search eventName=ConsoleLogin result=Failure| stats count by user, sourceIPAddressfillnull, mvexpand, makemv — tidy messy data
Section titled “fillnull, mvexpand, makemv — tidy messy data”index=firewall | fillnull value="unknown" user # replace nullsindex=proxy | makemv delim="," categories | mvexpand categories # split multivaluemacros — reuse SPL
Section titled “macros — reuse SPL”A macro is a saved SPL snippet you call with backticks, keeping detections DRY. Define windows_failed_logon once as index=wineventlog EventCode=4625 and reuse it:
`windows_failed_logon`| stats count by src_ipMacros are how mature Splunk content stays maintainable — change the definition once and every detection using it updates.
eval function mini-reference
Section titled “eval function mini-reference”if(cond, a, b) case(c1,v1, c2,v2, 1==1,default)coalesce(a,b,c) match(field,"regex")round(x,2) len(s) lower(s) strftime(_time,"%Y-%m-%d %H:%M")mvcount(mv) mvindex(mv,0) cidrmatch("10.0.0.0/8", ip)cidrmatch() deserves a callout — it is how you test whether an IP is in a subnet, essential for “external vs internal” logic in detections.
Part 11c: A Detection Gallery (Ten More, ATT&CK-Mapped)
Section titled “Part 11c: A Detection Gallery (Ten More, ATT&CK-Mapped)”Reps build fluency. Here are ten more production-flavored detections, each mapped to a technique; read them as patterns to adapt.
# 1. New service installed (T1543.003 Persistence)index=wineventlog (EventCode=7045 OR EventCode=4697)| table _time, host, Service_Name, Service_File_Name, user
# 2. Scheduled task created (T1053.005 Persistence)index=wineventlog EventCode=4698| table _time, host, Task_Name, user
# 3. New local/domain admin added (T1098 / privilege abuse)index=wineventlog (EventCode=4728 OR EventCode=4732) Group_Name="*Admins*"| table _time, host, Group_Name, Member_Name, Subject_User_Name
# 4. LSASS access by unusual process (T1003.001 Credential Access)index=sysmon EventCode=10 TargetImage="*\\lsass.exe"| search NOT (SourceImage IN ("*\\MsMpEng.exe","*\\csrss.exe","*\\wininit.exe"))| table _time, host, SourceImage, GrantedAccess
# 5. DCSync-like directory replication (T1003.006)index=wineventlog EventCode=4662 Properties="*1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*"| search NOT src_host IN (known_domain_controllers)| table _time, Account_Name, src_host
# 6. Suspicious LOLBins with network args (T1218 Defense Evasion)index=sysmon EventCode=1 (Image="*\\certutil.exe" OR Image="*\\bitsadmin.exe" OR Image="*\\mshta.exe") (CommandLine="*http*" OR CommandLine="*ftp*")| table _time, host, Image, CommandLine
# 7. RDP to servers at odd hours (T1021.001 Lateral Movement)index=wineventlog EventCode=4624 LogonType=10| eval hour=strftime(_time,"%H")| where hour < 6 OR hour > 22| stats count by user, src_ip, ComputerName
# 8. Mass file access / possible collection (T1039)index=wineventlog EventCode=4663 Object_Name="*\\Finance\\*"| stats dc(Object_Name) AS files by Account_Name| where files > 50
# 9. DNS tunneling suspicion (T1048 Exfiltration over DNS)index=dns| eval qlen=len(query)| stats avg(qlen) AS avg_len, count by src_ip| where avg_len > 50 AND count > 1000
# 10. Audit policy changed (T1562 Defense Evasion)index=wineventlog EventCode=4719| table _time, host, user, SubcategoryNotice the common shape: constrain to the relevant source, key on the behavior, then either table for hunting or stats ... | where for a thresholded alert. Adapt the field names to your TA and you have ten more heatmap cells covered.
Part 11d: Data Models, Acceleration, and tstats
Section titled “Part 11d: Data Models, Acceleration, and tstats”At enterprise scale, searching raw events is too slow for detections that must run every few minutes over billions of events. Splunk’s answer is data models and acceleration.
- A data model is a structured, hierarchical mapping of your data onto CIM (Chapter 2) — e.g., the “Authentication” data model normalizes every auth source into common fields (
Authentication.user,Authentication.action,Authentication.src). - Acceleration pre-computes summary indexes (tsidx) for a data model, so aggregate queries are near-instant.
tstatsqueries those accelerated summaries directly and is often 10–100× faster than the equivalent raw search.
# Fast: count failed auths by source across ALL auth sources via the data model| tstats count from datamodel=Authentication where Authentication.action=failure by Authentication.src, Authentication.user| rename Authentication.* AS *| where count > 30Splunk Enterprise Security’s out-of-the-box correlation searches are written this way — against accelerated CIM data models with tstats — which is why they can run continuously at scale. You do not need ES to benefit: accelerating a data model for your busiest sources (auth, network, endpoint) makes your own scheduled detections fast enough to run frequently. Blue team usage: if a scheduled detection is skipping runs because it takes too long, converting it to tstats against an accelerated data model is usually the fix.
Part 11e: Splunk Enterprise Security — The SIEM App
Section titled “Part 11e: Splunk Enterprise Security — The SIEM App”Core Splunk gives you search, alerts, and dashboards. Splunk Enterprise Security (ES) is the premium app that turns Splunk into a full SIEM with SOC-oriented workflow on top. You may not have ES in a home lab, but you will meet it on the job, so know the vocabulary.
- Notable Events — ES’s version of alerts: when a correlation search fires, it creates a Notable Event with severity, the entities involved, and the contributing evidence. These are the “alerts” a Tier 1 analyst triages (Chapter 1’s queue).
- Incident Review — the queue interface where analysts see, own, triage, and update Notable Events (assign, change status, add notes, escalate) — the alert lifecycle from Chapter 1 in a product.
- Correlation Searches — scheduled
tstats-backed detections (against CIM data models) that generate Notables; ES ships hundreds mapped to ATT&CK, and you write your own. - Risk-Based Alerting (RBA) / Risk Framework — the productized version of Part 8: detections assign risk to a risk object (user/host), and a risk-incident rule raises a Notable when accumulated risk crosses a threshold. This is the modern way ES fights alert fatigue.
- Adaptive Response — automated actions attached to Notables (enrich, disable an account, isolate a host via an EDR integration) — SOAR-style automation (Chapter 1) inside ES.
- Assets & Identities — lookup frameworks that enrich every event with asset criticality and user identity automatically — Chapter 1’s enrichment, built in.
- Security Posture / Glass Tables — executive dashboards summarizing the SOC’s state.
flowchart LR
CS[Correlation searches - tstats + CIM] --> NE[Notable Events]
RBA[Risk-based alerting] --> NE
AI[Assets & Identities enrichment] --> NE
NE --> IR[Incident Review queue]
IR --> AR[Adaptive Response: enrich / contain]
IR --> AN[Analyst triage + escalate]
The takeaway: ES is not a different skill — it is the Chapter 1 workflow (queue, triage, enrich, respond, RBA) wrapped around the SPL you already know. Everything you learned writing raw searches and the risk-index pattern is exactly what ES automates. If a job posting says “Splunk ES,” it means these concepts plus the SPL fundamentals of this chapter.
Part 11f: An Investigation Workflow Template
Section titled “Part 11f: An Investigation Workflow Template”To make triage repeatable in Splunk, keep a mental (or literal) template. For any host/user alert:
1. SCOPE index=* host=<h> OR user=<u> earliest=-24h | stats count by sourcetype2. EXECUTION index=sysmon host=<h> EventCode=1 | table _time Image ParentImage CommandLine3. NETWORK index=sysmon host=<h> EventCode=3 | table _time Image DestinationIp DestinationPort4. DNS index=dns host=<h> | stats count by query answer | sort - count5. AUTH index=wineventlog (EventCode=4624 OR 4625) user=<u> | table _time EventCode src_ip LogonType6. PERSIST index=wineventlog (EventCode=7045 OR 4697 OR 4698) host=<h>7. FLEET <pivot the IOC/domain/hash across all hosts>8. TIMELINE index=* host=<h> OR user=<u> earliest=<t0> latest=<t1> | sort _time9. MAP+ACT map to ATT&CK, disposition, escalate, operationalize as a detectionWorking from a template keeps you from tunnel-visioning on the first interesting event and forgetting to check persistence or scope the fleet — the disciplined triage from Chapter 1, expressed as a Splunk runbook.
Part 12: Detection & Defense Angle (Consolidated)
Section titled “Part 12: Detection & Defense Angle (Consolidated)”SPL is the instrument; here is how to wield it well as a defender.
Write detections mapped to techniques, not to one-off indicators. Every SPL detection should target an ATT&CK technique and carry its ID (Chapter 3). “Office spawns a script interpreter” (behavioral) ages far better than “block this hash” (indicator). SPL makes behavioral logic expressible — use that power.
Tune with precise exclusions, measured against both FP and FN. When a detection is noisy, narrow it with specific, verified-benign context (NOT (user=svc_scanner AND host=known_scanner)), never broad suppression. Track the false-positive rate (Chapter 1) as you tune, and ask each time whether the exclusion could hide a real attack.
Correlate for confidence via risk-based alerting. Individually weak signals combine into strong ones. Use the risk-index pattern (Part 8) so a chain of medium detections on one entity escalates as a single high-severity incident — this is both the alert-fatigue fix and the confidence boost.
Enrich in-search with lookups. Asset criticality, user role, and threat-intel lookups turn a bare event into a triage-ready alert (Chapter 1’s enrichment). Wire IOC lists in as lookups, but remember they are low on the Pyramid of Pain — enrichment, not your primary detection.
Schedule, throttle, and name alerts for the human. A detection that is not scheduled is a hunt, not a control. Schedule it, throttle to prevent storms, give it a clear name and a one-line “why this fired,” and link the playbook (Chapter 1). Design for the 3 a.m. analyst.
Hunt in SPL, then operationalize. Use interactive SPL to hunt hypotheses from threat intel (Chapter 3), and whenever a hunt finds something, convert it into a scheduled detection (Part 10, Step 7). That is how the SOC’s coverage grows with every investigation.
Monitor Splunk itself. A dead forwarder or a broken TA silently blinds you (Chapter 2’s source-health lesson). Use Splunk’s monitoring console and searches like | metadata type=hosts index=* to spot sources that stopped reporting.
Part 13: Final Revision / Summary
Section titled “Part 13: Final Revision / Summary”A SIEM collects, normalizes, stores, detects, and lets analysts investigate; Splunk is a market-leading SIEM built on schema-on-read — it stores raw events and extracts fields at search time, which is why it handles messy logs so well. Its architecture is forwarders (Universal/Heavy) shipping data to indexers (store + index into named indexes, tagged by sourcetype/source/host), searched from search heads running SPL. Getting data in (local install, forwarders, HEC, or BOTS datasets) is the prerequisite to practicing.
SPL is a pipeline language: a constrained base search (index/sourcetype/time) followed by commands joined by pipes, each transforming the results — the grep|awk|sort|uniq idea at scale. The core toolkit: search (filter), stats (aggregate — count, dc(), values(), earliest/latest), eval (compute fields, if/case), where (filter computed), rex (regex extraction), top/rare (frequency — rare is a hunting favorite), timechart (trends/beaconing), shaping commands (fields, rename, sort, dedup, head), lookup (enrichment/IOC matching), and transaction (grouping, used sparingly). Fields come from automatic extraction, Technology Add-ons, and manual rex; the CIM normalizes them so one search works across sources.
We built real, ATT&CK-mapped detections: brute force vs spraying (via dc(user)), success-after-brute-force, Office-spawns-interpreter, encoded PowerShell, Kerberoasting (RC4 4769), C2 beaconing (low-variance intervals), cleared logs (1102), and cloud exfil. Correlation/risk-based alerting (a risk index summed per entity) turns many weak signals into one high-confidence incident — the alert-fatigue fix. Detections become value only as scheduled, throttled, well-named alerts and focused dashboards. The lab hunted an intrusion end to end — scope, find execution, pivot to C2, hunt the IOC across the fleet, detect lateral movement and collection, assemble the timeline, and operationalize the hunt into a detection. Finally, good SPL hygiene — constrain the base search, filter early, prefer stats over join/transaction, use tstats at scale, avoid leading wildcards — is what makes detection fast enough to matter.
Part 14: Cheat Sheet / Quick Reference
Section titled “Part 14: Cheat Sheet / Quick Reference”Search structure
index=X sourcetype=Y earliest=-24h keyword field=value| command1 | command2 | ...Metadata fields
index·sourcetype(drives extraction) ·source·host·_time._rawholds the original event text;_timeis the event timestamp (the axis of everything).
Core commands
stats count/dc()/sum()/avg()/values()/earliest()/latest() by fieldeval newfield = if()/case()/round()/strftime()where <computed condition>rex "(?<name>regex)"— extract at search timetop/rarefield — frequency (rare = hunt)timechart span=1h count by field— trends/beaconingfields,rename x AS y,sort - field,dedup,head Nlookup table key OUTPUT cols— enrichment/IOCtransaction field maxpause=5m— group (sparingly)tstats— fast aggregation on accelerated data
Command families
- Streaming (
eval,rex,where,fields) · Transforming (stats,chart,timechart,top,rare) · Generating (tstats,inputlookup,makeresults) · Orchestrating (append,join,map).
Detection snippets (ATT&CK)
- Brute force:
EventCode=4625 | stats count values(user) dc(user) by src_ip - Spraying:
bucket _time span=10m | stats dc(user) by src_ip,_time - Office→script:
EventCode=1 ParentImage=*winword.exe Image=*powershell.exe - Encoded PS:
match(CommandLine,"(?i)-enc|iex|downloadstring") - Kerberoast:
EventCode=4769 Ticket_Encryption_Type=0x17 | stats dc(Service_Name) by Account_Name - Cleared logs:
EventCode=1102 - Cloud exfil:
dest_category=cloud_storage | stats sum(bytes_out) by user,dest
Alerting
- Save As → Alert → schedule (cron) → trigger (results>0) → throttle → action (email/ticket/webhook/notable).
- Throttle on the entity field (host/user) to prevent one incident from storming the queue.
Performance rules
- Name index+sourcetype+time · filter early,
| fieldsearly ·statsoverjoin/transaction·tstatsat scale · no leading*wildcard. - Field names are case-sensitive; search terms are not. Confirm names with the fields sidebar or
| fieldsummary.
Risk-based alerting
- Detections write to a
riskindex with score+entity → correlation search sums per entity → notable if over threshold.
More commands
eventstats(global aggregate onto each row) ·streamstats(cumulative/ordered — beacon intervals) ·bin/bucket(group time) ·spath(JSON) ·fillnull·mvexpand·[subsearch]·`macro`·tstats(accelerated).
Handy eval functions
if() case() coalesce() match() round() strftime() cidrmatch("10.0.0.0/8",ip) mvcount() len() lower().
Splunk ES vocabulary
- Notable Event (=alert) · Incident Review (=queue) · Correlation Search (=detection) · Risk Framework (=RBA) · Adaptive Response (=SOAR action) · Assets & Identities (=enrichment).
Investigation template
- Scope → Execution (Sysmon 1) → Network (Sysmon 3) → DNS → Auth (4624/4625) → Persistence (7045/4697/4698) → Fleet pivot → Timeline → Map+Act.
Where to find ready detections
- research.splunk.com (Splunk Security Content) — production SPL mapped to ATT&CK.
Time-range syntax
- Relative:
earliest=-24h latest=now·earliest=-7d@d(snap to day). - Absolute:
earliest="02/12/2027:08:00:00". - Always set a bound — unbounded searches scan all time.
Search modes
- Fast (skip field discovery, quickest) · Smart (default) · Verbose (all fields, slowest). Use Fast for known-field detections.
Part 15: Practice Labs & Resources
Section titled “Part 15: Practice Labs & Resources”- Splunk Boss of the SOC (BOTS) v1/v2/v3 (free datasets + guided questions) — the single best way to practice everything in this chapter against realistic attack data. Do the guided version, then the open scenario.
- Splunk Search Tutorial & free “Search Under the Hood” docs — the official SPL fundamentals.
- Splunk
search-referencedocs — the authoritative per-command reference; bookmarkstats,eval,rex,tstats,streamstats. - Splunk Fundamentals 1 (free eLearning) — structured intro to search, fields, and reporting.
- TryHackMe — Splunk: Basics, Splunk 2, Splunk 3, Investigating with Splunk, Benign rooms — hands-on SPL and investigation.
- TryHackMe — Splunk: Exploring SPL and Splunk: Data Manipulation — deeper drills on the transforming commands and field extraction from Parts 5–6.
- Boss of the SOC (BOTS) v3 “APT scenario” — a full multi-stage intrusion to hunt with the Part 11f template, ideal after finishing this chapter.
- BOTS-style datasets on your home lab — ingest your own Sysmon/Windows/firewall (Chapter 1 lab), run Atomic Red Team, and write the Part 7 detections against your own telemetry.
- Splunk Security Content (research.splunk.com) — a free library of detections (SPL) mapped to ATT&CK; read real, production-grade searches and adapt them. Pick five detections for techniques you know and study exactly how the SPL implements the behavior.
- The “Hunting with Splunk” blog series (Splunk) — practical threat-hunting walkthroughs that model the hypothesis→SPL→operationalize loop from Chapter 3.
- Splunk Common Information Model (CIM) docs and the Windows/Sysmon TAs — install and study how fields get normalized.
- Splunk Attack Range (github.com/splunk/attack_range) — a project that stands up a lab, runs attacks (Atomic Red Team/Caldera), and ships the telemetry into Splunk so you can practice detections against real attack data end to end.
- Splunk Lantern / Splunk Docs “Search Manual” — worked examples and the reference for every command in this chapter.
- BlueTeamLabs / CyberDefenders Splunk challenges — timed investigations that mirror real SOC work.
- Splunk4Ninjas / Splunk BOTS workshops — guided walkthroughs that explain the SPL behind each answer, ideal for reinforcing Part 5–7.
| makeresults+eval— practice building SPL logic with synthetic data when you have no dataset handy (great for testingcase(),cidrmatch(), regex).
Self-check
Section titled “Self-check”You should now be able to, without notes: describe Splunk’s forwarder/indexer/search-head architecture; explain schema-on-read and why sourcetype matters; write a base search that is fast (index+sourcetype+time); use stats/eval/where/rex/lookup fluently; tell brute force from spraying in SPL via dc(); build a beacon detection with streamstats; explain risk-based alerting with a risk index; and turn a hunt into a scheduled, throttled alert. If any is shaky, run the BOTS dataset and rewrite that Part’s examples against it — SPL is learned by typing it, not reading it.
Capstone exercise
Section titled “Capstone exercise”Load a BOTS dataset (or your own home-lab telemetry), and in one sitting: (1) pick one BOTS scenario, (2) reconstruct the attack with the Part 11f investigation template, (3) map each finding to ATT&CK, (4) write three technique-level detections from Part 7/11c against the data and confirm they fire, (5) wire one up as a scheduled alert with throttling, and (6) build a small dashboard panel showing failed-vs-successful logons over time. Completing this is a realistic portfolio piece you can describe in an interview.
One transferable-skill note before moving on. It is tempting to think you are learning “Splunk,” but what you are really learning is pipeline-based, aggregate-then-filter security analytics. That mental model — constrain the source, transform in stages, aggregate with stats, filter the result, correlate per entity — is universal. In the next chapter the pipe becomes | in KQL and stats becomes summarize, but the thinking is identical. Analysts who internalize the model, not just the syntax, pick up each new SIEM in days. So as you drill SPL, pay attention to the shape of the reasoning, not only the keywords.
In the next chapter we move to a second SIEM, Microsoft Sentinel and its query language KQL — cloud-native, tightly bound to Microsoft’s identity and endpoint telemetry, and increasingly common in enterprises. Everything you learned about pipeline thinking, aggregation, and technique-mapped detection transfers directly; only the syntax changes.
Practice questions
Section titled “Practice questions”- Write an SPL search that finds source IPs with more than 50 failed logons (4625) that were followed by at least one success (4624) for the same account. Explain which ATT&CK technique it targets.
- You need to distinguish password spraying from brute force in one dataset. Which
statsfunction is the key to telling them apart, and what threshold logic would you use for each? - A junior analyst writes
index=* powershell | stats count by hostand it takes ten minutes. Give three specific changes to make it fast, and explain why each helps. - Explain how you would implement risk-based alerting in Splunk using a risk index, and why it reduces alert fatigue compared to firing every individual detection.
- After hunting and finding a winword→powershell chain, describe exactly how you would turn that finding into a scheduled alert, including trigger condition and throttling, and why operationalizing the hunt matters.
- Your scheduled detection keeps skipping runs because it takes too long over billions of events. What Splunk feature would you use to fix it, and how does it work?
- Explain the difference between
stats,eventstats, andstreamstats, and give a security use case where you specifically needstreamstats.
Answer notes
Section titled “Answer notes”index=wineventlog (EventCode=4625 OR EventCode=4624) | stats count(eval(EventCode=4625)) AS fails count(eval(EventCode=4624)) AS successes by src_ip,user | where fails>50 AND successes>0. Targets T1110 Brute Force — specifically the dangerous success-after-many-failures outcome.dc(user)(distinct count of users) is the key. Brute force = highcountwithdc(user)≈ 1 (many attempts, one account); spraying = highdc(user)with low attempts-per-user (many accounts, few tries each) from one source in a short window.- (a) Name the index and sourcetype instead of
index=*(stops scanning all data); (b) add a time bound (only search the needed window); (c) anchor the term (powershell*not*powershell) and| fieldsearly — each reduces the volume Splunk must read and field-extract. - Each detection writes a risk event (score + risk_object) to a
riskindex viacollect; a correlation search sums risk per entity and raises a single Notable when it crosses a threshold or spans multiple tactics. It reduces fatigue because one correlated incident replaces a dozen scattered medium alerts, and it raises confidence because the combination is stronger evidence than any single signal. - Save the SPL (
EventCode=1 ParentImage=*winword.exe Image=*powershell.exe) as an alert, schedule it (e.g., every 5 minutes over the last 5 minutes), set the trigger to “number of results > 0,” add throttling (suppress repeats per host for, say, 60 minutes) so one incident doesn’t storm the queue, name it clearly with the ATT&CK ID, and attach the playbook. Operationalizing matters because a hunt catches one instance; a scheduled detection catches every future one automatically — the Chapter 1 feedback loop. - Data model acceleration +
tstats. A CIM data model is accelerated into pre-computed tsidx summaries;tstatsqueries those summaries directly instead of raw events, often 10–100× faster, letting the detection finish within its schedule. statscollapses events into a summary table;eventstatsadds a global aggregate as a field to every original row;streamstatsadds a cumulative/ordered aggregate row by row. You needstreamstatsto compute the time delta between consecutive connections per host (beacon-interval analysis), because that requires the previous event’s value on each current row in time order.
Discussion
Questions, corrections, or something to add? Comment with your GitHub account.