HackBlue

Tools · SIEM and detection engineering · 02 / 24

Splunk stats, timechart, and eval: turning raw events into detections

Use stats with dc, values, earliest, and latest, create fields with eval, chart activity over time, and combine them into a simple threshold detection.

11 min read · practice only with authorization

stats functions you will use daily

count counts events. dc(field) counts distinct values. values(field) lists unique values. earliest(_time) and latest(_time) give the first and last time seen. sum, avg, and max work on numbers. Grouping with by creates one row per combination.

Snippet
index=proxy earliest=-1d
| stats count dc(dest_host) as hosts values(category) as categories
        earliest(_time) as first latest(_time) as last by user
| eval first=strftime(first, "%F %T"), last=strftime(last, "%F %T")
| sort - hosts

eval and where

eval creates or changes fields with functions such as if, case, len, lower, round, and strftime. where filters with expressions after a transform, while search filters with keyword-style matching. A common detection pattern computes a ratio or a flag with eval and keeps rows with where.

Snippet
index=wineventlog EventCode IN (4624, 4625) earliest=-1h
| eval outcome=if(EventCode=4624, "success", "failure")
| stats count(eval(outcome="failure")) as fails count(eval(outcome="success")) as ok by src_ip
| where fails > 30 AND ok > 0

Lab: chart and threshold

Using a sample dataset with authentication or web logs, chart events over time per status and spot spikes. Then build a search that flags any source with more than 30 failures and at least one success in an hour, which is a password spraying or guessing pattern followed by a hit. Save it as an alert in your lab instance with a 1-hour schedule.

Snippet
index=botsv1 sourcetype=stream:http earliest=0
| timechart span=10m count by status limit=6

index=web earliest=-7d | bin _time span=1h | stats count by _time, src_ip | eventstats avg(count) as avg stdev(count) as sd by src_ip | where count > avg + 3*sd

From thresholds to baselines

Fixed thresholds are easy but brittle. eventstats and streamstats compute averages and deviations alongside each row, which lets you flag what is unusual for that source rather than using one number for everyone. Start simple, measure false positives, and add baselines where the noise is too high.

Diagram

stats functions at a glance

Pick the function that answers your question.

  • count

    How many events?

  • dc(field)

    How many distinct users, hosts, or IPs?

  • values(field)

    Which distinct values appeared?

  • earliest / latest

    When did this start and stop?

  • avg / stdev with eventstats

    Is this row unusual compared to the baseline?

Check yourself

1. Which stats function counts distinct values of a field?

2. After stats, which command filters rows with an expression such as fails > 30?

3. Which command adds aggregate values such as an average to every row without collapsing them?