Splunk SPL — detection engineering
Author and review SPL for Splunk Enterprise / Enterprise Security. The discipline here mirrors the rigour applied to KQL in
— filter early, structure deliberately, and document every assumption.
Pair with:
(EventCode mapping),
(hunt→rule lifecycle),
(
),
(conceptual KQL translation).
Distilled from: Analysis of 2009 production detections, 234 macros, and 104 lookups in
splunk/security_content (ESCU v5.26+).
1. Search-time discipline
1.1 Always lead with index, sourcetype, time
Splunk searches without an
constraint scan default indexes — in production, there is often no default, so nothing is searched.
spl
index=wineventlog sourcetype="WinEventLog:Security" earliest=-7d@d latest=now
| Construct | Use |
|---|
| Primary partition — always include |
| Schema-level filter |
| , | Index-time fields — fast |
| / | Event-time bound — prefer snapped () |
| / | Index-time bound — late-arriving WEL from remote hosts |
Full rejection checklist:
references/Anti-Patterns.md
(AP-S1, AP-S2).
1.2 Indexed vs search-time fields
Index-time (TSIDX — usable with
): metadata fields plus anything in
.
Search-time: everything else — extracted per event from
/
.
Filter on index-time fields first; defer search-time parsing to the last possible moment.
spl
// GOOD
index=wineventlog sourcetype="WinEventLog:Security" EventCode=4688 TERM(encodedcommand) CommandLine="*-encodedcommand*"
// BAD
index=wineventlog EventCode=4688
| eval command=CommandLine
| where match(command, "-encodedcommand")
Leading wildcards (
) cannot use tsidx alone — pair with
or
on known tokens (AP-S12). Example:
TERM(mshta) TERM(exe) CommandLine="*\\mshta.exe"
.
1.3 Streaming vs transforming commands
Streaming (
,
,
,
) parallelises across indexers. Transforming (
,
,
,
) collects to the search head.
spl
// GOOD: filter and project before stats
index=wineventlog EventCode=4688 TERM(encodedcommand) CommandLine="*-encodedcommand*"
| eval ParentName=lower(ParentImage)
| fields _time, host, User, CommandLine, ParentName
| stats count by host, ParentName
2. Schema authority — non-negotiable
Index names, sourcetypes, and CIM field availability are
tenant-specific. Never assume
or that CIM fields exist without verification.
Procedure before authoring tstats from datamodel=...
:
- Confirm sourcetype→CIM mapping via Technology Add-ons (TAs) in the target deployment.
- Verify data model acceleration in ES → Settings → Data Models.
- Smoke-test with
| tstats summariesonly=true count from datamodel=<Model> earliest=-1h
.
- Zero results with known activity → broken mapping, not "no threats" (AP-S11).
For Windows
filters, cross-reference
— map Event IDs to tenant index/sourcetype via Layer 1 macros, not hardcoded indexes.
Load full CIM catalogue and TA prerequisites:
references/CIM-Data-Models.md
.
3. vs vs vs
| Command | Source | Speed | Use when |
|---|
| Raw events | Slowest | Fields not in tsidx / DM |
| TSIDX or accelerated CIM DM | 10–100× faster | Index-time fields, , accelerated models |
| Streaming state | Fast | Stateful per-event windows (beaconing, sequences) |
| Metrics indexes (MCatalog) | Fast for metrics | Pre-aggregated numeric time series — not event indexes |
spl
| tstats summariesonly=true count from datamodel=Endpoint.Processes
where Processes.process_name=powershell.exe
by Processes.dest, Processes.user
Always pair
on accelerated data models with
— without it, Splunk falls back to raw events for unaccelerated ranges.
details:
references/Best-Practices.md
.
4. Lookups, eventtypes, macros
Lookups
spl
... | lookup threat_intel_feed indicator AS src_ip OUTPUT category, severity
Prefer
/
over subsearches for IOC correlation (AP-S4).
Three-layer macro architecture
Production frameworks (ESCU, OpenTide) use three layers for portability:
| Layer | Suffix | Purpose |
|---|
| 1 — Source | , | Index/sourcetype abstraction (, ) |
| 2 — Process | | Binary matching with anti-evasion ( + ) |
| 3 — Filter | | FP exclusions — empty by default () |
Principle: detections are write-once, deploy-anywhere. Customers tune macros, not core SPL.
Tenant content repos may ship
— treat as authoritative override for Layer 1/2/3 definitions when present.
5. Canonical + CIM pattern
Single canonical block — detection variants in
references/Detection-Type-Patterns.md
.
spl
| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime
FROM datamodel=Endpoint.Processes
WHERE <filter_conditions>
BY Processes.dest, Processes.user, Processes.process_name, Processes.process, Processes.parent_process_name
| `drop_dm_object_name(Processes)`
| `security_content_ctime(firstTime)`
| `security_content_ctime(lastTime)`
| <post_processing>
| `<detection_name>_filter`
ESCU vs OpenTide macro mapping:
references/CIM-Data-Models.md
.
6. Enterprise Security: correlation searches
Lifecycle
- Hunting search validated ad-hoc.
- Saved as scheduled correlation search (typically 5–15 min, lookback aligned).
- Adaptive Response: notable (), risk boost (), TTP annotation.
- Tuning via thresholds, suppression, Layer 3 filter macros.
- RBA: low-fidelity signals aggregate per-entity risk; notables for high-fidelity or cumulative threshold breach.
Scheduled vs real-time constraints
| Mode | Guidance |
|---|
| Scheduled | Default. Supports heavy , summary indexes, long lookback. Align cron to lookback + ingestion lag. |
| Real-time | Continuous window (1–5 min). Avoid , large subsearches, unbounded . |
| Throttling | Mandatory on production rules — set entity fields (, , ) and duration. |
Notable and RBA field shaping
spl
... | eval rule_name="Suspicious PowerShell Execution", severity=high, src=host, user=User, signature="T1059.001"
spl
... | eval risk_message="Unusual login from impossible-travel pair",
risk_object=user, risk_object_type="user", risk_score=40,
threat_object=src_ip, threat_object_type="ip_address"
Detection type shapes (TTP, Anomaly, Hunting, Correlation, Baseline):
references/Detection-Type-Patterns.md
.
Common idioms (beaconing, impossible travel):
.
7. False-positive engineering
Surface tuning knobs explicitly — align with Layer 3 filter macros for production, inline blocks for hunt iteration:
spl
index=wineventlog sourcetype="WinEventLog:Security" EventCode=4688 earliest=-7d@d
| where match(CommandLine, "(?i)-(enc|encodedcommand)")
``` --- BEGIN ENVIRONMENT FILTERS (move to Layer 3 filter macro for production) --- ```
| where NOT match(user, "(?i)^svc_")
| where NOT match(host, "(?i)^SCAN-")
``` --- END ENVIRONMENT FILTERS --- ```
| stats count by host, User, CommandLine
Production: migrate exclusions to
`<detection_name>_filter`
macro.
Hunting: inline filters acceptable with documented rationale.
8. IOC query templates
Replace index/sourcetype with tenant Layer 1 macros.
spl
// IP address — lookup enrichment
`proxy_logs` earliest=-30d
| lookup threat_intel_feed indicator AS dest_ip OUTPUT category, severity
| where isnotnull(category)
| table _time, src_ip, dest_ip, category, url
// Domain — base search + match
`dns_logs` earliest=-30d
| where match(query, "(?i)(malicious-domain\.example|bad-domain\.example)")
// Hash — process or file events
`endpoint_logs` earliest=-30d
| where sha256 IN ("<sha256_1>", "<sha256_2>")
| table _time, dest, user, file_name, sha256, process
// Base64-encoded PowerShell pipeline
`win_security_logs` EventCode=4688 earliest=-14d
| where match(CommandLine, "(?i)(-enc|-encodedcommand|-e )")
| rex field=CommandLine "(?i)-e(?:nc|ncodedcommand)?\s+(?<EncodedBlock>[^\s]+)"
| eval DecodedCommand=`base64decode(EncodedBlock)`
| where isnotnull(DecodedCommand)
| where match(DecodedCommand, "(?i)(IWR|DownloadString|IEX)")
9. Rationale and precision/recall scoring
When SPL feeds hunt records or MDR
/
, the rationale must answer:
- Why these indexes/sourcetypes? What captures the observable.
- Why these filters? Map each filter to intelligence or behavioural reasoning.
- What the query does NOT cover. Variants, evasion, scope exclusions.
- How results connect to the hypothesis. What a true positive proves.
| Score | Precision (FP volume in clean env) | Recall risk |
|---|
| HIGH | < 10 results; filters trace to specific intel | Narrow filters; variants may evade |
| MEDIUM | 10–100; mix of specific + behavioural | Known patterns covered; novel variants may evade |
| LOW | > 100; broad behavioural pattern | Broad detection; harder to evade |
10. SPL vs KQL — conceptual translation
| KQL | SPL |
|---|
where TimeGenerated > ago(7d)
| |
| or CIM tagged search |
| |
| |
summarize count() by X, bin(Time, 5m)
| bin _time span=5m | stats count by X, _time
|
summarize arg_max(Time, *) by Key
| or |
| Avoid — use , , or accelerated DMs |
| Summary indexing or tstats from datamodel=...
|
| |
| |
| Early or saved macro |
| Comments | Triple-backtick block comments (see §11 below) |
Mindset shift:
in SPL is expensive at scale. Prefer
, union +
, or accelerated CIM data models.
11. Comment discipline
Splunk inline comments use triple backticks:
spl
``` Detection: Suspicious encoded PowerShell ```
``` Source: <reference> ```
``` MITRE: T1059.001 ```
index=wineventlog sourcetype="WinEventLog:Security" EventCode=4688 earliest=-7d@d
| where match(CommandLine, "(?i)-(enc|encodedcommand)")
| stats count by host, User, CommandLine
Apply the same header structure used in KQL (Hunt, Source, MITRE, Platform). Within OpenTide MDR objects, inline SPL headers may be reduced — metadata lives in MDR
and alert templates.
12. Quality checklist
Universal
ESCU / OpenTide macro conventions
13. Mapping into OpenTide MDR
When SPL is wired into
in an OpenTide MDR object, coordinate with
for schema fields:
| MDR field | SPL / ES alignment |
|---|
| SPL block scalar — follow this skill |
| Integer match count before alert |
| / | Entity dedup — mirror ES throttling |
| / / | Align to ingestion lag and where needed |
| / | field substitution |
| / | Secondary investigation search |
| , , — document score rationale (AP-S10) |
| , (e.g. , ) |
Description, tuning narrative, severity, and response procedure → MDR
and
. Hunt-to-rule conversion:
.
14. Reference catalogues — load when…
| Need | File |
|---|
| CIM model fields, TA prerequisites, macro mapping | references/CIM-Data-Models.md
|
| TTP / Anomaly / Hunting / RBA / Baseline shapes | references/Detection-Type-Patterns.md
|
| Beaconing, impossible travel, rare-process | |
| / function lookup | references/Eval-and-Stats-Functions.md
|
| TERM/PREFIX, subsearch limits, summary indexing, mstats, scheduling | references/Best-Practices.md
|
| Rejection checklist AP-S1–S12 | references/Anti-Patterns.md
|