Skip to content
back to the map
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.

cloudtrail-pivot.shbash
# 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 -rn
merge_timeline.pypython
import 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.

suspicious-encoded-powershell.yml (Sigma)yaml
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: medium

Habits

  • 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

updated on 14/10/2026 · sources to go deeper · report an error

in other domains

Unlocks

test what you learned

Loading questions…