The Case & Rules
3 minYou're the on-call analyst. An alert fired overnight. You have three logs from the affected server. Your job: figure out what happened, which accounts/systems are affected, and write it up so the team can respond.
- 🥉 Bronze — identify the attacker IP and the attack type.
- 🥈 Silver — reconstruct the timeline and name every compromised account.
- 🥇 Gold — produce a complete incident report with scope, impact, and recommended actions, all in clean code.
You'll generate the three synthetic logs below (so the case is reproducible), then analyse them with the tools from Lessons 22-25. Everything is local, synthetic data — pure practice.
Generate the Evidence
5 minRun this once to create the three log files for the case (auth, web, and a file-integrity report). It embeds a realistic attack amid normal noise.
import random from datetime import datetime, timedelta from pathlib import Path base = datetime(2026, 5, 28, 2, 0, 0) NORMAL_IPS = ["10.0.0.5", "10.0.0.8", "10.0.0.12"] ATTACKER = "203.0.113.66" auth, web = [], [] # normal background traffic for i in range(200): t = base + timedelta(minutes=random.randint(0, 600)) ip = random.choice(NORMAL_IPS); user = random.choice(["aisha", "ben", "lee"]) auth.append(f"{t:%Y-%m-%d %H:%M:%S} sshd: Accepted password for {user} from {ip}") # THE ATTACK — brute force then success then web activity attack_start = base + timedelta(minutes=74) # 03:14 for i in range(300): # brute force burst t = attack_start + timedelta(seconds=i * 0.2) auth.append(f"{t:%Y-%m-%d %H:%M:%S} sshd: Failed password for admin from {ATTACKER}") crack = attack_start + timedelta(seconds=61) auth.append(f"{crack:%Y-%m-%d %H:%M:%S} sshd: Accepted password for admin from {ATTACKER}") for path, code in [("/admin", 200), ("/admin/users", 200), ("/uploads/shell.php", 200), ("/etc/passwd", 403)]: t = crack + timedelta(seconds=random.randint(30, 300)) web.append(f"{t:%Y-%m-%d %H:%M:%S} {ATTACKER} GET {path} {code}") random.shuffle(auth) # logs aren't sorted in real life Path("auth.log").write_text("\n".join(auth)) Path("access.log").write_text("\n".join(web)) Path("fim_report.txt").write_text( "MODIFIED /var/www/index.php\nADDED /var/www/uploads/shell.php\n") print("evidence written: auth.log, access.log, fim_report.txt")
Analyse these three files and answer: Who attacked (IP)? How (attack type)? When (timeline)? What did they compromise (account + what they accessed)? What should the team do now?
Event 1 · Find the Attacker (Bronze)
14 minParse auth.log and identify the source IP responsible for an abnormal number of failed logins, and the attack type. (Lesson 25's brute-force detector.)
Show the medal solution
import re from collections import Counter from datetime import datetime LINE = re.compile(r"(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) sshd: " r"(?P<result>Failed|Accepted) password for (?P<user>\S+) " r"from (?P<ip>\d+\.\d+\.\d+\.\d+)") records = [] for line in open("auth.log"): m = LINE.search(line) if m: records.append({"ts": datetime.strptime(m["ts"], "%Y-%m-%d %H:%M:%S"), "result": m["result"], "user": m["user"], "ip": m["ip"]}) fails = Counter(r["ip"] for r in records if r["result"] == "Failed") attacker, count = fails.most_common(1)[0] print(f"🥉 Attacker IP: {attacker} ({count} failed logins) → BRUTE FORCE")
🥉 The IP with hundreds of failures stands out instantly from the handful of normal IPs.
Event 2 · Timeline & Compromise (Silver)
12 minEstablish when the brute force ran, whether it succeeded (a failures→success correlation), and which account was cracked. Then correlate with access.log to see what the attacker did after getting in.
Show the medal solution
# correlation: did the attacker's failures turn into a success? attacker_events = sorted([r for r in records if r["ip"] == attacker], key=lambda r: r["ts"]) fails = [e for e in attacker_events if e["result"] == "Failed"] success = next((e for e in attacker_events if e["result"] == "Accepted"), None) print(f"🥈 Brute force: {fails[0]['ts']} → {fails[-1]['ts']} ({len(fails)} attempts)") if success: print(f"🥈 CRACKED account '{success['user']}' at {success['ts']}") # what did they do after? (web log, same IP) import re WEB = re.compile(r"(?P<ts>[\d\- :]{19}) (?P<ip>\S+) (?P<method>\S+) " r"(?P<path>\S+) (?P<code>\d+)") print("🥈 Post-compromise web activity:") for line in sorted(open("access.log")): m = WEB.search(line) if m and m["ip"] == attacker: print(f" {m['ts']} {m['method']} {m['path']} → {m['code']}")
🥈 The success right after a flood of failures = the brute force worked; the web log shows the attacker reaching /admin and uploading shell.php.
Event 3 · Scope & Impact (toward Gold)
13 minBring in the third source — the file-integrity report — to determine what was left behind, and assess impact. Cross-reference: does the FIM finding match the web activity?
Read fim_report.txt. Does the ADDED file match anything in the web log? What does the MODIFIED file imply? What was the attacker likely trying to access with the 403'd request?
Show the analysis
fim = open("fim_report.txt").read() print("🥇 File integrity findings:\n", fim) # Cross-reference: shell.php appears in BOTH the FIM report (ADDED) # and the web log (POST/GET) — confirming a webshell was uploaded AND used. # The MODIFIED index.php suggests defacement or a backdoor injection. # The 403 on /etc/passwd shows an attempt to read system accounts (failed). print(""" 🥇 IMPACT ASSESSMENT - Account 'admin' compromised via SSH brute force. - Webshell uploaded (uploads/shell.php) and accessed → remote code execution. - index.php modified → possible backdoor/defacement. - Attempted /etc/passwd read (403, blocked) → enumeration attempt. Scope: this web server + the 'admin' account. Assume full server compromise. """)
🥇 The strongest evidence is corroboration across sources: shell.php in the FIM report AND the web log = a confirmed, used webshell.
Grand Final · The Incident Report (Gold)
12 minThe medal event. Produce a single automated script that ingests all three logs and outputs a complete incident report: summary, attacker, attack type, timeline, compromised accounts, scope/impact, and recommended remediation. Clean, correct, reproducible.
Show the medal solution
import re from collections import Counter from datetime import datetime def load_auth(): L = re.compile(r"(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) sshd: " r"(?P<r>Failed|Accepted) password for (?P<u>\S+) " r"from (?P<ip>\d+\.\d+\.\d+\.\d+)") out = [] for line in open("auth.log"): m = L.search(line) if m: out.append((datetime.strptime(m["ts"], "%Y-%m-%d %H:%M:%S"), m["r"], m["u"], m["ip"])) return out def report(): auth = load_auth() fails = Counter(ip for _, r, _, ip in auth if r == "Failed") attacker, n = fails.most_common(1)[0] ev = sorted([a for a in auth if a[3] == attacker]) cracked = next((u for ts, r, u, ip in ev if r == "Accepted"), None) first = ev[0][0]; success_ts = next((ts for ts, r, *_ in ev if r=="Accepted"), None) web = [l.strip() for l in open("access.log") if attacker in l] fim = open("fim_report.txt").read().strip() print("=" * 60) print("INCIDENT REPORT — SSH brute force → web compromise") print("=" * 60) print(f"Attacker IP : {attacker}") print(f"Attack type : SSH password brute force ({n} failed attempts)") print(f"Timeline : first attempt {first}; cracked at {success_ts}") print(f"Compromised : account '{cracked}'") print("Post-exploit (web log):") for w in sorted(web): print(" ", w) print("File integrity changes:") for line in fim.splitlines(): print(" ", line) print("\nIMPACT : RCE via uploaded webshell; assume full server compromise.") print("ACTIONS :") for a in ["Isolate the server from the network", "Disable/rotate the 'admin' account; enforce keys + MFA", "Ban 203.0.113.66; review firewall/SSH exposure", "Remove uploads/shell.php; restore index.php from clean backup", "Rotate all secrets the 'admin' session could reach", "Preserve logs as evidence; full forensic review"]: print(" -", a) report()
🥇 Gold = one reproducible script producing a complete, correct report from all three sources, with actionable remediation. This is real incident-response output.
Recap & Scorecard
3 minYou ran a real incident investigation: parse three log sources, detect the brute force, correlate failures→success→webshell across auth, web, and FIM logs, and report with scope, impact, and remediation. The decisive move was corroboration — shell.php appearing in both the file-integrity report and the web log turned suspicion into proof. This is the daily work of a SOC analyst, built entirely from Lessons 22-25. Tally your medals, and note which step you found hardest — that's where to practise.
Pattern Card
- parse
- Structure each log source with regex into queryable records.
- detect
- Find the anomaly: the IP with abnormal failure counts.
- correlate
- Link events across sources/time into one attack story.
- report
- Summary + timeline + scope + impact + remediation.
Homework
4 minRedo the Grand Final from scratch without peeking, timing yourself. Then design your own scenario: modify the evidence generator to embed a different attack (e.g. a stolen-then-misused valid account with no brute force, or data exfiltration via large outbound transfers in a network log), and write the detector + report that catches it. Note which signals gave it away.
Sample · a self-designed scenario
Scenario: "Insider misuse" — no brute force.
Evidence: a VALID account ('lee') logs in successfully, but from a
brand-new IP, at 04:00 (outside lee's usual 09:00-18:00), then the
web log shows it downloading /reports/all_customers.csv repeatedly.
What gives it away (no failures to catch!):
- off-hours success for a 9-5 user (baseline anomaly, L8-25)
- login from an IP never seen for that user
- bulk access to a sensitive export → data exfiltration pattern
Detector: build a per-user "normal hours + known IPs" baseline,
flag successes outside it, then correlate with large/sensitive
data access in the web log.
Lesson: brute force is loud; insider/credential misuse is QUIET —
you only catch it with baselines and correlation, not failure counts.Non-negotiables: a re-solved Grand Final and an original scenario whose detector relies on different signals (baselines/correlation, not just failure counts).