DRAFT — internal staging preview. Not for distribution until owner-approved.
Zero to SOC starting…

Zero to SOC · Free open lesson series · Lesson 7 of 7

Lesson 7: Follow the Thread

FreeNo signupRuns in your browserNo install

Lesson 7 of 7 · Investigation arc

Time: ~60–75 minutes, one sitting.

You will be able to:

Prerequisites: Lessons 1–6. You paste back: the parser and time helpers (parse, when, hour_of), plus a trimmed weak-signal core of Lesson 6's risk engine (risk) to reproduce the deploy alert the investigation starts from. Python used: collections.defaultdict, set/dict comprehensions, sorted with key=, f-string alignment ({...:<8}), list concatenation, appending to a file.

The free seventh lesson in Zero to SOC. It aims to be the clearest explanation of why

an alert is a lead, not a verdict — and the one move that turns it into the real story

— that exists anywhere. If it isn't, it isn't done.

Start here: this lesson continues directly from Lesson 1

through Lesson 6. You'll reuse the parser and the

risk engine you built. If you haven't done those, do them first — each takes one sitting.


Where Lessons 1–6 left off

Six lessons, and you can detect. Burst, correlation, baseline, distributed, low-and-slow, chaining, risk scoring — your panel catches attacks that a lot of people holding the job title would miss. In Lesson 6 the risk engine fired a clean alert on deploy: a stealth login, no failures, off-hours, from a never-seen IP. Score 55. High.

And then what?

Every lesson so far ends the moment the alert fires. But in a real SOC, the alert firing is where the work starts, not where it ends. An alert is a machine tapping you on the shoulder and saying "look here." It is not an answer. Your boss doesn't want to know that a rule fired. Your boss wants to know three things, right now:

  1. Did they actually get in?
  2. What else did they touch?
  3. What do we do in the next ten minutes?

The alert answers none of those. It names one account and stops. This lesson is about turning that one alert into the answers — and it starts by refusing to trust the alert's own scope.

Lessons 1–6 are the detection arc; Lesson 7 opens the investigation arc.

LessonThe skill it adds
1. Build a SIEM from scratchingest → parse → detect → alert
2. Catch the breachcorrelate events; baseline what's normal
3. Defeat the counterpivot the group-by key
4. The patient attackerstretch the horizon; count occasions
5. The drip that landedchain detectors — rules that read alerts
6. The login that looked fineweigh weak signals; sum the risk
7. Follow the threadinvestigate: pivot, timeline, blast radius← you are here

The problem: the alert names one thing; the attacker touched more

Here's the trap, and it's the opposite of the traps in every earlier lesson. Before, the danger was missing the attack. Now the attack is caught — and the danger is believing the alert is the incident. It almost never is.

An alert fires on deploy. But the attacker who got into deploy didn't teleport in and stop. They came from somewhere, and once inside they moved — reused that foothold to reach the next account, and the next. The alert sees one symptom. The incident is the whole body.

The move that finds the rest is the oldest move in investigation: pivot. The alert hands you one fact — an account, and the hostile IP it was accessed from. You don't stop there. You turn the question around and ask it of the attacker's artifact: what else did that IP do? That IP is attacker-controlled ground; everything it touched is suspect. Pull that thread and the real shape of the incident comes out of the log.


The data

Same format as always. A Jun 15 baseline of known-good logins, some normal traffic — and, buried in it, the incident. deploy is accessed from a never-seen IP at 3 a.m. (the Lesson 6 alert). What the alert doesn't tell you is on the next three lines: the same IP then fails against dbadmin twice and finally gets in. One alert, two break-ins.

Make an auth.log with this content:

auth.log — edit freely, then create it

Eight lines. The deploy login is the one your risk engine flagged. The three dbadmin lines right after it are the part the alert never mentions — and notice they're deliberately not loud enough for any rule you own: two failures (no burst, no grind), one IP (not distributed). dbadmin gets compromised in total silence. The only thing tying it to the alert is the address it came from. (All IPs are from ranges reserved for documentation, so they're safe as examples.)


Recap: your parser and the alert

Paste the parser and the Lesson 6 risk engine back in (trimmed to the weak signals — that's all we need to reproduce the alert). Nothing new yet.

Python — editable, runs in your browser

Run it:

Expected output
risk alerts: {'deploy': 55}

There's your alert — and there's the whole problem in one line of output. The engine says deploy, score 55, and nothing else. dbadmin? The risk engine never flagged it: dbadmin isn't on the sensitive list, so its 3 a.m. login from a new IP scores 25 + 15 = 40, under the line. The account that actually holds your database got silently owned, and your detection panel is quiet about it. If you close the ticket on deploy and go back to bed, you've missed half the breach. The alert didn't lie. It just wasn't the whole truth, and no alert ever is.


The new idea: pivot on the attacker's ground, then build the timeline

Stop asking "is this account bad?" — you already know it is; that's why the alert fired. Start asking a different question: what is connected to this?

The alert gives you two facts about deploy: it was successfully accessed, and the access came from an IP you've never seen for it. That IP is the thread. It's the one thing you know is attacker-controlled, and unlike the account, the attacker can't un-touch the other things they did from it. So you pivot: take the hostile IP and pull every event that IP produced — against any account. Every account that IP successfully logged into is now suspect, whether or not a rule ever fired on it.

Then you do the second half of investigation, the part that turns a pile of connected events into a story a human can act on: you build a timeline. Sort everything involving the hostile IP or a compromised account into one chronological list. That timeline is the incident — first contact, the moves, the last thing seen — and it's what your RCA and your "what do we do" both come out of.

Pivot to find the scope; timeline to tell the story. That's the whole craft of first-line investigation, and it's about fifteen lines of Python.


Investigation: from one alert to the whole incident

investigate takes the alerting account and reconstructs the incident around it: confirm the break-in, find the hostile IPs, pivot on them to find every compromised account, assemble the timeline, and hand back the actions an analyst takes in the next ten minutes.

Python — editable, runs in your browser

Run it on the account the alert named:

Python — editable, runs in your browser

Output:

Expected output
alert_account: deploy
got_in: True
hostile_ips: ['203.0.113.91']
accounts_compromised: ['dbadmin', 'deploy']
blast_radius: 2
first_activity: Jun 23 03:14:09
last_activity: Jun 23 04:05:00
timeline:
  Jun 23 03:14:09  Accepted deploy   from 203.0.113.91
  Jun 23 03:58:12  Failed   dbadmin  from 203.0.113.91
  Jun 23 04:01:40  Failed   dbadmin  from 203.0.113.91
  Jun 23 04:05:00  Accepted dbadmin  from 203.0.113.91
recommended_actions:
  - disable account: dbadmin
  - disable account: deploy
  - block source IP: 203.0.113.91
  - rotate credentials for all affected accounts
  - preserve auth.log for the incident record

Read what just happened. You started with an alert that said one word — deploy — and you walked out with the incident: two accounts compromised, not one; the attacker's address; the exact minute they first got in and the exact minute they reached your database account; and a list of what to do about it. dbadmin — the account no rule ever flagged — is right there in accounts_compromised, surfaced purely because it shared an IP with the thing that was flagged. That is the entire difference between "a rule fired" and "we understand what happened." One is a machine's tap on the shoulder. The other is an analyst's answer.

And notice the timeline reads like a sentence: at 03:14 they got into deploy; forty minutes later they used that same foothold to start hammering dbadmin; by 04:05 they were in there too. You didn't write that story. You pulled one thread and the log told it to you.


Now generate the attack yourself

Same loop as every lesson — but flipped. Before, you played the attacker to test a detector. This time you play the attacker to test your investigation: keep moving, and watch the investigation keep up. Add one more hop — the same hostile IP reaches a third account:

Python — editable, runs in your browser

Output:

Expected output
accounts_compromised: ['dbadmin', 'deploy', 'webapp']
blast_radius: 3
last_activity: Jun 23 04:40:15

You moved the attacker one more account down the line, and the investigation followed without you touching it — still starting from the same single deploy alert. That's the payoff of pivoting on the attacker's ground instead of the alert's scope: the alert stays frozen at one account forever, but the thread grows to whatever the attacker actually did. Three accounts now, and the last-seen time moved to 04:40 — the incident is still unfolding, and your report knows it.


Make it real: where this breaks

A seventh working tool, and — like every lesson — it catches the clean case and the real world has edges. These are the ones that matter most, because getting an investigation wrong gets someone fired or gets an incident missed.

Logins are not actions. This is the big one. Your log shows that dbadmin was accessed — it says nothing about what the attacker did once inside. Did they read the database? Copy it? Plant a backdoor? Nothing? The auth log can't tell you, because it stops at the door. Answering "what did they do in there" needs a different kind of telemetry — process creation, file access, network connections — and that's exactly where the course goes next (the forensics act: watch an attack at the process level, with native tools, no black boxes). The pivot finds the rooms the attacker entered; forensics tells you what they touched in each.

The pivot key can lie. We pivoted on IP, and IP is a decent thread — but a shared office NAT, a corporate VPN, or a cloud egress address can put a hundred innocent users behind the same IP. Pivot on that and you'll "compromise" your whole company. Real investigation corroborates: same IP and an odd time and a sensitive target and a first-seen pattern. One shared artifact is a lead, not a conviction — the same lesson as risk scoring (Lesson 6), now applied to investigation.

A careful attacker switches artifacts. The whole method rests on the attacker reusing one IP. A good one won't — they'll come at dbadmin from a different address so the IP-pivot never connects the two. Then you pivot on something else they did have to reuse: the account they landed on, the time window, a stolen session, later a process or a file hash. Investigation is a chain of pivots on whatever the attacker couldn't vary — exactly the "pick the key they don't control" instinct from Lesson 3, now as a manual craft instead of a rule.

**Your sensitive list will never be complete — which is why you investigate.** dbadmin should obviously have been on the sensitive list, and wasn't. That gap is normal; no config is ever complete. Detection missed dbadmin because of it. Investigation caught dbadmin anyway, because investigation doesn't depend on having pre-labeled the important things — it follows the attacker to them. That's the deepest reason a SOC needs analysts and not just rules: the rules encode what you already knew to worry about; the investigation finds what you didn't.

That move — refuse to trust the alert's scope, pivot on the attacker's own artifacts, and let the timeline tell the story — is the second half of the job. Lessons 1–6 taught you to make the alarm ring. This one taught you what to do when it does.


Check yourself

Before moving on — answer from your head, then verify against your code:

  1. After you append the webapp line, what does investigate(events, "deploy") report for blast_radius and last_activity?
  2. Why does the investigation pivot on the hostile IP instead of just digging deeper into deploy, the account the alert named?
  3. Rewrite the incident so the attacker hits dbadmin from a different IP than the one that took deploy. What does the report say now, and why?
Answers
  1. blast_radius: 3 and last_activity: Jun 23 04:40:15. The webapp login shares 203.0.113.91, so the pivot picks it up with no code change, and the timeline's last event moves to the new line.
  2. Because the alert's scope is one symptom, and everything about deploy is already in it. The hostile IP is the attacker's own ground — every account it touched is suspect whether or not a rule fired. That's what surfaces dbadmin, which detection genuinely missed: it scores 25 + 15 = 40, under the risk line, because it was never on the sensitive list. Only the shared IP connects it to the incident.
  3. The pivot loses dbadmin: hostile_ips is still ['203.0.113.91'] (built from deploy's first-seen success), but no dbadmin event carries that IP, so accounts_compromised is ['deploy'] and blast_radius is 1 — verified. The whole method rests on the attacker reusing one artifact; when they don't, you pivot on something else they had to reuse — the account, the time window, later a process or a hash.

What you just did, and what's next

You crossed the line that separates a detection engineer from a SOC analyst: an alert is a lead, not a verdict — so you pivot on the attacker's own ground and let the log reconstruct the incident the alert only hinted at. You took a one-word alert (deploy), pulled the thread of the hostile IP, and walked out with two compromised accounts, a minute-by-minute timeline, and a response plan — then made the attacker keep moving and watched the investigation grow to meet them. That is the daily work of the job, built from scratch, no black boxes.

This is the Zero to SOC flagship turning the corner from detection to response — the same path, one step further: ingest, parse, detect, correlate, score… now investigate. From here the course keeps going the same way:

once inside? (Watch an attack at the process level with native tools — course Act III.)

defending it live, which is exactly the capstone (Capstone_Zero_To_SOC.md).

multi-hop ones — at your lab so no two students investigate the same breach.

defend the findings — a portfolio artifact, not a multiple-choice score.

If these seven lessons were clear, that's the whole course: hard things explained plainly, built by your own hands, no magic.


Free to use and share. This series is open-source on purpose — it's the clearest way we know to show what the flagship is. If it helped, that's the pitch.

← Previous lesson Back to the series →