A fixed 1 GB egress threshold is noisy for engineers and blind for a finance clerk. User and entity behaviour analytics replaces it with a baseline per entity, a comparison to peers with the same role, and awareness of weekly seasonality. You can build a defensible version in a SIEM without a dedicated UEBA product.
Signals at a Glance
| Signal | Where | ATT&CK |
|---|---|---|
| Daily outbound bytes more than 3 standard deviations above the entity’s own baseline | Proxy, firewall | T1030 |
| Volume far above the entity’s peer group median | Proxy, identity data | T1030 |
| Download volume from file shares or SaaS spike before resignation or role change | File audit, SaaS audit | T1213 |
| First-time large uploads to a category never used by the entity | Proxy categories | T1567 |
| Anomalous series flagged by seasonal decomposition | Time-series analytics | T1030 |
Time-Series Anomalies
Per-entity series with weekly seasonality are straightforward in KQL. The function below decomposes each user’s daily outbound bytes and flags points the model cannot explain:
// Sentinel (KQL) - per-user daily upload anomalies with seasonality
// tune/validate: BytesSent and UserName depend on your proxy schema
let start = ago(45d);
let end = now();
ProxyLogs
| where TimeGenerated between (start .. end)
| make-series bytes = sum(BytesSent) on TimeGenerated from start to end step 1d by UserName
| extend (anomalies, score, baseline) = series_decompose_anomalies(bytes, 2.5, 7, "linefit")
| mv-expand TimeGenerated to typeof(datetime), bytes to typeof(long), anomalies to typeof(int),
score to typeof(double), baseline to typeof(long)
| where anomalies == 1 and TimeGenerated > ago(2d) and bytes > 500000000
| project TimeGenerated, UserName, bytes, baseline, score
| order by score desc
A peer-group comparison catches the entity whose history is already inflated. Join on role or department and use a median-based score so a few heavy users do not mask each other:
index=proxy earliest=-7d@d | stats sum(bytes_out) as user_bytes by user, _time span=1d | lookup hr_roles user OUTPUT department | eventstats median(user_bytes) as peer_median stdev(user_bytes) as peer_sd by department, _time | eval peer_z = (user_bytes - peer_median) / max(peer_sd, 1) | where peer_z > 4 AND user_bytes > 500000000 | sort - peer_z ``` tune/validate: the hr_roles lookup is yours to build from the directory ```
Operating the Detection
- Rank, do not page: surface the top 10 deviations per day with the destination categories attached for an analyst to review.
- Use risk scoring so a volume anomaly alone adds a few points, while volume plus archive creation plus off-hours activity crosses the alert threshold.
- Account for events that legitimately change baselines: migrations, month-end close, onboarding and project launches. Let owners tag them for the SOC.
- Retain at least 45 days of per-entity history, so one weekend or one holiday does not distort the baseline.
Splunk ESCU analytic stories on data exfiltration and risk-based alerting, Microsoft Sentinel UEBA and KQL time-series functions, Elastic machine-learning job guidance, and MITRE CAR analytics on data volume. Baseline your own environment — model quality depends on clean role data and a history that is representative.
Build Detections That Fire
Mjolnir Security helps blue teams turn threat intelligence into high-fidelity detections across endpoint, network, identity and cloud telemetry.
- Detection Engineering We build and tune detections mapped to ATT&CK from your own telemetry.
- Threat Hunting as a Service Hypothesis-driven hunts for the behaviours on this page.
- 24/7 Incident Response Containment when a detection fires. Call +1 833 403 5875.
Stay ahead of emerging threats. Get notified when we publish new intelligence reports and advisories.
Subscribe to Alerts