Chapter 15 — Threat intelligence and OSINT: guided lab
Lesson map
Lesson 15 published. Map, example, EX043.
flowchart TD
N0["Learning outcomes"]
N1["15.1 Lab setup"]
N2["Lab — matching a log"]
N3["15.2 Reading the result"]
N4["15.3 Exact versus substring"]
N5["15.4 Hand-off"]
N6["15.5 What a hit means"]
N7["15.6 Repeat lookups"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7
N7 --> EX["Worked example"]
EX --> Q["Exercise EX043"]
Logical drawing
flowchart LR
N0["Learning outcomes"]
N1["15.1 Lab setup"]
N2["Lab — matching a log"]
N3["15.2 Reading the result"]
N4["15.3 Exact versus substring"]
N5["15.4 Hand-off"]
N6["15.5 What a hit means"]
N7["15.6 Repeat lookups"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7 --> Q["EX043"]
[Learning outcomes]
|
v
[15.1 Lab setup]
|
v
[Lab — matching a log]
|
v
[15.2 Reading the result]
|
v
[15.3 Exact versus substring]
|
v
[15.4 Hand-off]
|
v
[15.5 What a hit means]
|
v
[15.6 Repeat lookups]
|
v
[Worked example] --> [Exercise EX043]
Learning outcomes
You will (1) match indicators against a synthetic DNS log; (2) summarise hits per host with first-seen times; (3) avoid false matches from partial strings; (4) turn results into a hand-off for responders.
15.1 Lab setup
The lab uses a synthetic resolver log and two indicators. It runs offline in the course virtual machine with the standard library. Students must not paste real CMS logs into lab files.
Keeping real CMS logs out of lab files protects staff privacy and avoids copying council data into teaching material that might be shared. The synthetic log has six lines, which is small enough for a student to check every output by hand. Running offline means the lab cannot contact any of the names it contains.
Lab — matching a log
"""Lab (ch 15): match a synthetic DNS log against an indicator list and report hits per host."""
iocs = {"pay-portal.example", "update-svc.example"}
log = ["2026-09-04T08:01Z ws-114 intranet.example", "2026-09-04T08:03Z ws-114 pay-portal.example",
"2026-09-04T08:05Z ws-207 news.example", "2026-09-04T09:12Z ws-207 update-svc.example",
"2026-09-04T09:13Z ws-207 update-svc.example", "2026-09-04T10:40Z ws-031 pay-portal.example.org"]
hits = {}
for line in log:
t, host, q = line.split()
if q in iocs:
hits.setdefault(host, []).append((t, q))
for h, v in sorted(hits.items()):
print(h, len(v), "first", v[0][0], v[0][1])
print("non-match kept for review: pay-portal.example.org (suffix differs)")
Run with python3 lab_match.py. Verified output:
ws-114 1 first 2026-09-04T08:03Z pay-portal.example
ws-207 2 first 2026-09-04T09:12Z update-svc.example
non-match kept for review: pay-portal.example.org (suffix differs)
15.2 Reading the result
ws-114 looked up pay-portal.example once, first at 08:03 UTC. ws-207 looked up update-svc.example twice, first at 09:12. A lookup of pay-portal.example.org was not matched: it contains the indicator as a substring but is a different domain. The script prints it for review rather than ignoring it silently.
Lena checks the printout against the six lines: two exact matches for ws-207 and one for ws-114, with the 10:40 lookup by ws-031 excluded. The first times printed come from the first matching line for each host. The near miss is printed by a fixed statement in the lab rather than detected by the code, a simplification a real script would replace with a lookalike check.
15.3 Exact versus substring
Substring matching would flag pay-portal.example.org and perhaps legitimate names. Exact matching on the full name, after normalisation, avoids false alarms. Separate rules can watch for lookalikes deliberately.
A substring check for pay-portal.example would also match pay-portal.example.org and any longer name containing that text, so every such lookup would look like contact with the fraud domain. Exact matching on the full name keeps each reported hit precise. Lookalike detection is a different question with different tolerance for noise, so it gets its own rule and review.
15.4 Hand-off
Responders need host, indicator, first and last time, count and source of the indicator with its TLP. They do not need the script.
Each item in the hand-off answers a responder's question: which machine, what name, when it started, how often, and how much to trust the source and how widely to share. The worked example below gives all of these for both hosts in two lines. The script itself stays with Lena, because responders act on results, not code.
15.5 What a hit means
A DNS lookup shows that a machine asked for a name. It does not prove a connection succeeded or a user entered data. Proxy logs and user contact follow.
For ws-114, the lookup at 08:03 shows that the machine asked for pay-portal.example. The proxy log can show whether a page was then fetched, and only the user can say whether anything was typed into it. Lena's hand-off therefore asks the analyst to check proxy logs and contact users, rather than reporting a compromise.
15.6 Repeat lookups
ws-207's two lookups a minute apart may be one browser retry or software checking repeatedly. Repeated automatic lookups over hours would suggest installed software rather than a single click.
Two lookups is too few to tell which explanation is right, so Lena reports the count and timing without choosing. Proxy logs for ws-207 around 09:12 can show whether anything was fetched, and further lookups later in the day would point towards software. The vendor's TLP:AMBER label on update-svc.example means the details stay inside CMS.
15.7 Lab report
Students record inputs, the command run, the output, interpretation and one improvement they would make, such as reading last-seen times as well as first-seen.
The suggested improvement fits this lab: it prints only the first time per host, so a reader cannot tell when lookups stopped. Adding last-seen would show whether ws-207 kept looking up update-svc.example after 09:13. Recording inputs and the exact command lets a marker rerun the lab and get the same output. The improvement is written as a proposal, not applied to the lab, so the marked output stays the one embedded in the course.
Worked example
Lena's hand-off: "ws-114 — pay-portal.example (MCX, TLP:GREEN) — 1 lookup at 08:03 UTC. ws-207 — update-svc.example (vendor, TLP:AMBER) — 2 lookups from 09:12 UTC. Please check proxy logs and contact users. One near-miss name (pay-portal.example.org) listed for analyst review."
Synthetic case. The hosts and log are invented teaching material.
Errors and limits
Logs without client identity (shared resolvers, NAT) cannot pinpoint machines.
Exercises
- EX043 — Lab hits. Report hits per host from the lab with first-seen times. Rubric: 3 = ws-114 1 at 08:03 pay-portal; ws-207 2 from 09:12 update-svc; 2 = one host; 1 = wrong; 0 = none.
- EX044 — Substring trap. Why was pay-portal.example.org not matched? Rubric: 3 = different domain; exact match avoids false alarms; 2 = partial; 1 = bug; 0 = none.
- EX045 — Lookup meaning. What does a DNS hit not prove? Rubric: 3 = connection success or data entry; needs proxy/user follow-up; 2 = partial; 1 = proves compromise; 0 = none.
Synthesis
Match exactly, summarise per host, flag near misses for review and hand off what responders need.
Continue studying — chapter 15 of 30
← Chapter 14 (draft) · Course chapter index · Chapter 16 (draft) →