Reading logs for triage
Follow one suspicious login through identity, endpoint and cloud logs. Anatomy of a useful log line, why you read sequences instead of errors, time and normalisation traps, and the shell and SIEM queries that get you to the answer.
01Anatomy of a useful log line
- Read a log line for actor, action, target, outcome, time
- Reconstruct a sequence across sources
- Avoid the time and normalisation traps that wreck timelines
- Pivot with shell one-liners and SIEM queries on huge data
{"time":"2026-11-13T03:14:07Z","class":"authentication","actor":{"user":"priya@cyart.io","ip":"203.0.113.7","device_id":"unknown"},
"target":{"app":"okta"},"outcome":"success","reason":"mfa_push_approved","session_id":"s_7fa1","geo":"RO"}Every useful security log answers five things: who (actor identity and where from), did what (action), to what (target), with what result (outcome and reason), when (UTC, precise). This one says: at 03:14 UTC, Priya's account authenticated successfully from Romania on an unknown device, via an approved MFA push. Each of those facts alone is explainable. Together they are a hypothesis: MFA fatigue from a stolen password.
| Field | Pivot to | Question |
|---|---|---|
| session_id | Everything that session did in Okta and downstream apps | What was accessed after the login? |
| ip | All logins from this IP across users | Is this one account or a campaign? |
| user | Last 30 days of logins | Has this user ever been in RO or on an unknown device? |
| time | EDR and cloud audit ±15 min | What happened on their laptop and in AWS around then? |
Interviewers hand you one log line and ask "what does this tell you?". Strong candidates read the five fields aloud, say which are unusual, and name the next two pivots. Weak ones jump straight to a verdict.
02Read sequences, not errors
Beginners grep for ERROR and failure. Attackers mostly generate successes. Read the sequence: failed pushes, then an approved push at 3 a.m., then a new mail-forwarding rule, then a download of 400 files from SharePoint. No single line is an error.
A timeline is a table with four columns: UTC time, source, entity, event. Add rows from every source you touch, sort by time, and the story writes itself. Gaps are findings too: a host that goes silent for 20 minutes during an intrusion may have had its agent killed.
"I don't look for errors, I build the timeline. Six denied pushes then an approval at 3 a.m. from a new country is a fatigue attack until proven otherwise, and the mail rule five minutes later proves it."
Which log line is the strongest single indicator of compromise here?
03Time, normalisation and the traps
Most broken timelines are broken by time, not by attackers. Sources log in different zones, different formats and with clocks that drift. Normalise before you reason.
| Trap | What it looks like | Fix |
|---|---|---|
| Local time zones | Firewall in IST, IdP in UTC: the login appears 5.5 hours after the download | Convert everything to UTC on ingest; keep the original field too |
| Clock skew | Process starts before the login that spawned it | Check NTP health; prefer the collector's receive time as a tiebreak |
| Ingest delay | Search for the last 15 minutes misses events still in the pipeline | Search on event time with a margin; know each source's lag |
| Field name drift | src_ip, client.ip, IpAddress across three sources | Map to one schema (ECS, OCSF) in the pipeline, not in your head |
| Retention | The VPN log you need rotated out after 7 days | Know retention per source before the incident, not during it |
- ACTOR
- SERVICE
- STREAM
- Live flow
- Lookup / async
No hits in the SIEM can mean nothing happened, or that the source was never onboarded, the parser broke, or the data aged out. Before you write "no activity observed", confirm the source had data for that host and window.
04Pivoting at scale: shell and SIEM
You will meet logs as compressed files on a box and as tables in a SIEM. The moves are the same: filter early, project the fields you need, count, sort, look at the rare values.
# One user's identity timeline, compact
zstdcat okta-2026-11-13.jsonl.zst | jq -r 'select(.actor.user=="priya@cyart.io") | "\(.time) \(.outcome) \(.reason // "") \(.actor.ip) \(.geo // "")"' | sort
# All users touched by the suspicious IP
zstdcat okta-*.jsonl.zst | jq -r 'select(.actor.ip=="203.0.113.7") | .actor.user' | sort | uniq -c | sort -rn | head
# Huge rotated files: never cat; stream and filter early
zgrep -h '"session_id":"s_7fa1"' app-*.log.gz | sort -k1 | less// Same pivot in a SIEM (Microsoft Sentinel, KQL)
SigninLogs
| where TimeGenerated between (datetime(2026-11-13 02:00) .. datetime(2026-11-13 05:00))
| where IPAddress == "203.0.113.7"
| summarize attempts=count(), ok=countif(ResultType == "0"), users=make_set(UserPrincipalName) by IPAddress, Location
| order by attempts descsort | uniq -c | sort -rn is the most useful pipeline in security. It turns a million lines into a frequency table, and the interesting values usually sit at the bottom (rare) or the top (noisy attacker).
"My first three pivots on any suspicious login are the session, the IP across all users, and the user's own 30-day history. That tells me in five minutes whether it is one confused user, one compromised account, or a campaign."
Q1What is the first thing you do once you believe it?+
Contain: revoke the session and all refresh tokens, reset the password, remove the mail rule, then investigate the downloads. Evidence first: export the logs to the case before anything rotates.
Q2Logs are in six consoles. How do you cope?+
Normalise into one schema with a shared time base, which is why the SIEM pipeline exists. Until then, a spreadsheet timeline in UTC with a source column.
Q3How do you know a source is complete?+
Check volume over time for that source and host. A sudden drop to zero is a pipeline or agent problem, and possibly tampering, not a quiet night.
A SIEM search for a host's process events during the incident window returns nothing. What do you check first?