technique
stage: detect
2 min read
Log analysis and building a timeline
Every investigation ends as a timeline: first access, what they touched, how they moved, what they took. The quality of that timeline depends on clean timestamps and good pivots.
SIEM (Splunk, Elastic, Sentinel)jqTimesketchSigmaCloudTrail / audit logsPlaso (log2timeline)
Start with clean time
- Convert every timestamp to UTC before you compare anything. Time zones and daylight saving cause most timeline errors.
- Note clock skew: a host with a wrong clock shifts its whole story. Compare a shared event across sources to measure the offset.
- Record the source, the field and the time resolution for each event; a log with only minute resolution cannot order events within the minute.
- Keep raw copies and work on copies.
Pivoting
An investigation moves by pivoting: take one known fact and ask what else shares it. Each pivot narrows or widens the scope.
- From an IP to every account that signed in from it, and every request it made.
- From an account to its sessions, its devices, the resources it touched, and the changes it made.
- From a host to its parent and child processes, network connections and files created.
- From a file hash to every host where it appeared.
- From the first malicious event backwards: how did the access begin?
Example: a compromised cloud identity
Pull everything one identity did from CloudTrail, in order, and look for firsts: a new source address, a new region, a sensitive API call.
# every action by the suspect identity, in time order
jq -r '.Records[]
| select(.userIdentity.arn // "" | test("build-bot"))
| [.eventTime, .eventName, .awsRegion, .sourceIPAddress, (.errorCode // "ok")] | @tsv' \
cloudtrail/*.json | sort
# which source addresses did this identity use?
jq -r '.Records[] | select(.userIdentity.arn // "" | test("build-bot")) | .sourceIPAddress' \
cloudtrail/*.json | sort | uniq -c | sort -rnimport csv, heapq
from datetime import datetime, timezone
def load(path, source, ts_field, fmt):
with open(path, newline="") as f:
for row in csv.DictReader(f):
ts = datetime.strptime(row[ts_field], fmt).replace(tzinfo=timezone.utc)
yield ts, source, row.get("message", "")
streams = [
load("idp.csv", "idp", "time", "%Y-%m-%dT%H:%M:%SZ"),
load("proxy.csv", "proxy", "ts", "%Y-%m-%d %H:%M:%S"),
]
for ts, source, msg in heapq.merge(*(sorted(s) for s in streams)):
print(ts.isoformat(), source, msg)Turn findings into detections
When you find a technique the attacker used, write a rule so it is caught next time. Sigma is a vendor-neutral format that converts to many SIEM query languages.
title: PowerShell launched with an encoded command
status: experimental
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\powershell.exe'
CommandLine|contains:
- ' -enc '
- ' -EncodedCommand '
condition: selection
falsepositives:
- Some management tools use encoded commands; baseline them first
level: mediumHabits
- Write the timeline as you go, with the evidence for each line.
- Separate facts from hypotheses and mark which is which.
- Look for the absence of logs too: a gap can be the attacker's cleanup.
- Keep a list of questions you cannot yet answer, and of logs you wish you had; that list improves your logging.
connections on the map
- Threat huntingrelies onLog analysis and building a timeline
- Log analysis and building a timelinescopesTriage, scoping and containment
updated on 14/10/2026 · sources to go deeper · report an error
in other domains
- Edge and application telemetrysecopsyou can only reconstruct what was collected
Unlocks
test what you learned
Loading questions…