Week 2, Practical — Threat Modelling on Galway Notes
Cybersecurity & Secure Programming
Pencil first, keyboard never. This is the practical that pays off Chapters 4 and 5. Two lecture hours of threat-modelling theory (Shostack’s Four Questions, the DFD, STRIDE, LINDDUN, PASTA) land here on a real codebase. The deliverable is a STRIDE table on paper plus a four-way decision (eliminate / mitigate / transfer / accept) on the top three threats. No exploit code. No curl. No sqlmap. The exploitation comes in Week 3 — today is design-time reasoning against the same application the cohort will exploit, statically scan, dynamically scan and fix across the rest of the term.
Slides
- Download
wk02_practical.pptx— open in PowerPoint, Keynote, or LibreOffice Impress to deliver. Speaker notes carry the timing cues and “stop pencils” prompts. - Download
wk02_practical.pdf— PDF export for printing or quick reference.
Before the practical
Students should arrive with:
The Week 1 lab environment working — Kali VM with Docker, the
clean-installsnapshot, two-NIC network. If the verification table from Week 1 is not green, fix that first. This practical assumes it.The book chapter (Chapter 6 — Week 2, Practical: Threat-Modelling Galway Notes) read in advance. The chapter contains the canonical twenty-three-row STRIDE table; the lab is reasoning, not exposition.
The
Cybersecurity_Bookrepository cloned, withapps/galway-notes/on disk:git clone https://github.com/atu-comp09031/Cybersecurity_Book.git cd Cybersecurity_Book/apps/galway-notes docker compose upThat brings up three containers —
app(Flask onhttp://localhost:5000),db(Postgres 16), andadmin_service(internal Flask on:5050, only reachable from inside the Compose network). Confirm the landing page loads athttp://localhost:5000.Demo accounts are seeded into the database on first start (per
SPEC.md§11):Role Email Password Student student@atu.ieStudent123!Staff staff@atu.ieStaff123!You do not have to use the running app today, but having it up means a question like “what does the import-from-URL form actually look like?” is answered in ten seconds rather than ten minutes.
apps/galway-notes/docs/architecture.mdopen in a tab. This is the artefact you start from. Read it end-to-end before the lab — it is short on purpose.The OWASP Threat Modeling Cheat Sheet open as one A4 reference page mapping onto Shostack’s Four Questions: https://cheatsheetseries.owasp.org/cheatsheets/Threat_Modeling_Cheat_Sheet.html
Pen and paper. Non-negotiable. The first forty minutes of any threat-modelling session belongs on paper, not in a tool. A3 if you can; A4 stapled together if not.
You do not need: the source code open, exploit tools, ZAP, or curl. All of that returns from Week 3 onwards.
Why Galway Notes replaces OWASP Juice Shop. The previous version of this practical ran a guided walkthrough of OWASP Juice Shop. Juice Shop is a fine vulnerable target, but threat-modelling a black-box Node app the cohort has never seen wastes the first thirty minutes on architectural reading-comprehension. Galway Notes is the codebase the same cohort will exploit in W3, fix in W4, statically scan in W5, dynamically scan in W6 — so the architectural reading carries forward. The threats you identify today come back as live exploitation targets next week.
Lab activity
The chapter walks the two-hour flow minute by minute and the lecturer’s full plan lives in apps/galway-notes/docs/walkthroughs/wk02-practical.md. The page below is the public-facing summary; the walkthrough is the authoritative source. Students work in pairs for the whole session.
| Phase | Time | What happens |
|---|---|---|
| 1. Read the system | 0:00–0:10 | Read architecture.md end-to-end. List the components on paper. Mark the five trust boundaries (TB1–TB5). Sketch the Level-1 DFD. |
| 2. Pencil-first STRIDE pass | 0:10–0:50 | Walk the DFD flow by flow. For each (flow x STRIDE letter) that lands, write a row. Pick six to eight flows from the shortlist; quality over coverage. |
| 3. Marquee finding | 0:50–1:00 | Lecturer-led. Open apps/galway-notes/secrets/jwt.key on the projector. Walk the eliminate-not-mitigate argument live. |
| 4. Compare against canonical | 1:00–1:20 | Project apps/galway-notes/docs/threat-model.md. Walk the canonical twenty-three-row table, focusing on rows the room missed (Repudiation, background threats). |
| 5. Deliverable & submission | 1:20–1:35 | Each pair submits their STRIDE table + top-three four-way decisions. Photo of paper or markdown file — legibility is the only formal requirement. |
Phase 1 — Read the system (10 min)
The architecture document is short on purpose. As you read, do three things in order:
- List the components. You should end up with:
app(Flask, port 5000),db(Postgres 16),admin_service(internal Flask, port 5050),attachments_volume, and three external actors — User, Attacker, Staff. - Mark the trust boundaries. Five matter: TB1 (browser to
app), TB2 (apptodb), TB3 (apptoadmin_service), TB4 (appto remote URL on the public internet), TB5 (appto attachments volume). - Sketch the DFD. Copy the level-1 diagram in
architecture.md§5 onto your paper. The exact shapes do not matter; what matters is that the ten numbered flows are visible and you can point at any one of them.
A DFD without trust boundaries is decorative. Every interesting threat sits on a flow that crosses one.
Phase 2 — Pencil-first STRIDE pass (40 min, in pairs)
Walk the DFD flow by flow, and at each flow ask Shostack’s four questions. Pick six to eight flows from the shortlist below and walk those — you will not get through every flow in forty minutes, and you do not need to.
| # | Flow | Trust boundary | STRIDE letters likely to land |
|---|---|---|---|
| 1 | Flow 1 — login / signup (User to app) |
TB1 | S, T, R, E |
| 2 | Flow 2 — session response (app to User) |
TB1 outbound | I, T |
| 3 | Flow 3 — note read / write SQL (app to db) |
TB2 | T, I, E |
| 4 | Flow 5 / 6 — attachment write & read (app to volume) |
TB5 | T, I |
| 5 | Flow 7 — URL-import outbound (app to remote URL) |
TB4 | I, E, D |
| 6 | Flow 9 — admin export request (app to admin_service) |
TB3 | S, R, I |
| 7 | PDF export sub-flow (process-local shell-out inside app) |
(process boundary) | E |
| 8 | JWT signing key (configuration secret, not on a flow line) | — | S, R, E |
A pair walking five flows in depth is doing the right work. A pair filling every cell of a 6-by-8 grid produces forty-eight vague rows — pull them back.
Concrete language
A threat description must say what an attacker does and what they get. “An attacker increments the integer note ID and reads someone else’s notes” is a threat. “Notes might be exposed” is not. (Chapter 4: “a vague threat is not a threat”.)
A worked example to anchor on
Resist the marquee — start with Flow 3 on TB2, the flow most students under-explore.
Flow 3, Tampering. The free-text search route composes SQL by string-concatenating
q. An attacker submitsq=x') OR 1=1 --, the predicate becomesWHERE owner_id = me.id AND (title LIKE '%x') OR 1=1 --%'), and the response renders rows the attacker does not own. Decision: mitigate. Replace concatenation with parameterised SQLAlchemytext(... :q ...)binding. Severity: high — confidentiality and integrity both fail on the same flow.
That is one row. You need at least five more.
Phase 3 — The marquee finding (10 min, lecturer-led)
BUG-05 — the marquee learning moment. The JWT signing key is a real secret committed to source at apps/galway-notes/secrets/jwt.key. Anyone with the repository — every student in the cohort, every fork, every CI cache — has the key. They can forge tokens that the server cryptographically trusts: defeating both authentication and repudiation. The “oh” moment when students realise this is the pedagogical payload of the practical.
The mitigation is not “rotate the key occasionally”. It is eliminate. A rate limiter on /api/v1/auth/token does not help — the attacker is not calling that endpoint, they are forging tokens locally. A web application firewall does not help — a forged token is, by every observable property, a valid token. An audit log does not help — the audit log records the forged identity and trusts it.
The only honest decision is eliminate: rotate the key out-of-band, move the new value to an environment variable loaded from a .env file in .gitignore, and audit the git history. Secrets in source defeat both authentication and the ability to investigate authentication failures after the fact. That is why this row is in the eliminate column, not the mitigate column.
Phase 4 — Compare against the canonical (20 min)
Project apps/galway-notes/docs/threat-model.md §3 — the canonical twenty-three-row STRIDE table. Walk it row by row at roughly thirty seconds per row, focusing on:
- Rows the room got. Acknowledge them. T01 (SQLi on search) is the cleanest tampering threat in the system; every pair should have it.
- Rows the room missed. These are the teaching points. The systematic gaps cluster on Repudiation (T05’s repudiation angle, T19’s missing audit log) and on the background threats (T18 brute-force credential stuffing, T20 plain-HTTP transport, T22 unbounded uploads). Spend more time here than on the rows the room got.
- Rows where the decision differs. A pair that put T04 (command injection in
/notes/<id>/export.pdf) under mitigate gets a focused conversation about why we eliminate a shell-out rather than escape its arguments — same logic as the marquee, different surface.
Two cross-cuts to draw on the board (both from threat-model.md §4). First, the Information Disclosure column is the longest, which is normal in a web app and is also where the IDOR threat (T09a) sits — the same authorisation defect at the heart of the Optus 2022 breach. Second, Repudiation is the shortest, with two rows; that is short for the right reason.
The four-way decision is the frame, not DREAD. The chapter is explicit: “TBD” is a row where the threat model has failed. Eliminate is rare — four rows in the canonical list (T04 cmdi, T05 JWT key, T08 alg=none, T14 verbose errors), all cases where the design choice itself is the bug. Mitigate is the modal answer. Transfer is empty in v1 — we have no insurer or managed service to push threats to. Accept is honest scope-setting (T18, T19, T20).
Deliverable
Each pair submits two artefacts. Deadline: end of the lab slot. This is a formative deliverable; late submissions are accepted but not flagged.
Artefact 1 — the STRIDE table. Six rows minimum, on paper or as a markdown file. Columns: number, STRIDE letter(s), threat description, affected route or component, decision, rationale. Photo of paper is fine; legibility is the only requirement.
Artefact 2 — top-three four-way decisions. Pick the three threats from your table you judge highest-impact. For each, write a paragraph (3–5 sentences) on:
- Which of eliminate / mitigate / transfer / accept you chose, and why.
- What the implementation of that decision would look like in code or in configuration. Concretely: “replace the string-concatenated SQL with
select(Note).where(...)so SQLAlchemy parameter-binds:q”, not “fix the SQLi”. - Why the alternative decisions are wrong for this threat. (For the marquee, this is the mitigation is the wrong frame paragraph from
threat-model.md§5.)
Submission: one Moodle upload per pair, or one Git pull request per pair against the cohort GitHub Classroom repository — lecturer’s preference for the cohort. Both names on the submission.
Rubric
The bar is did the pair do threat modelling, not did the pair find every threat.
| Criterion | Required to pass |
|---|---|
| STRIDE table size | At least six rows, each one (flow x STRIDE letter). Fewer than six = the pair did not walk enough flows. |
| Decision diversity | At least one eliminate decision and at least one mitigate decision in the top three. Six mitigate rows = the eliminate-vs-mitigate distinction has been skipped. |
| Marquee threat identified | The submission identifies BUG-05 (JWT signing key in source) or an equivalent secret-in-source threat with the eliminate decision. |
| Concrete language | At least three of the six rows describe an attacker action, not a state. “An attacker submits ?next=... and is redirected to a phishing page” passes; “the redirect parameter is unsafe” fails. |
| Plausible rationale | Each top-three decision paragraph names a control or design change a developer could implement on Monday morning. “Add input validation” is a wish; “reject next values whose host does not match request.host” is a control. |
A submission that hits all five passes. A submission that misses one — typically the marquee — gets a one-paragraph email-back from the lecturer pointing at the row in threat-model.md and is invited to resubmit.
Common student questions
These come up reliably; recognise the symptom and resolve in seconds rather than minutes.
- “Are we supposed to exploit anything today?” — No. This lab is design-time reasoning. The exploitation phase is Week 3 onwards. If you find yourself reaching for
curlor the running app, you are doing the wrong lab. - “Where do I draw the trust boundaries?” — Across any line where the level of trust changes. Browser →
app(TB1),app→db(TB2),app→admin_service(TB3),app→ remote URL (TB4),app→ attachments volume (TB5). The five-boundary skeleton inarchitecture.md§4 is the minimum. - “Is this an architecture diagram or a DFD?” — A DFD has directional, labelled arrows and explicit trust boundaries. A box labelled
Flaskwith an unlabelled line to a box labelledPostgresis an architecture sketch. If your diagram could appear unchanged in a marketing deck, it isn’t a DFD. - “We have eighteen threats but no decisions — is that fine?” — No. A threat list without decisions is a wishlist, and it gets ignored. Every row needs Mitigate / Eliminate / Transfer / Accept, plus an owner role.
- “Why isn’t the JWT signing key on a flow line in the DFD?” — Because it is a configuration secret, not a flow secret — it never leaves
app’s process memory in normal operation. That non-appearance on the diagram is itself a hint about Phase 3: the asset matters even though no flow line touches it. - “Can I peek at
threat-model.mdwhile we work?” — No. The pedagogical value depends on you generating threats by reasoning, not by reading the answer key. After the session — yes, read it end-to-end; it is the bridge from today’s lab to Week 3.
End-of-practical self-check
Optional, formative — not graded. Confirms the conceptual material from the lab landed.
Going further
These are optional and entirely up to you. Each is good. None is required.
- Book Chapter 4 — Threat Modelling. The lecture-week reading. Re-read “Where threat modelling fails” before submitting your top-three decisions.
- Book Chapter 6 — Week 2, Practical: Threat-Modelling Galway Notes. The chapter contains the full canonical twenty-three-row STRIDE table and the per-row exploit / fix outline.
- Shostack, Adam. Threat Modeling: Designing for Security (Wiley, 2014). The standard reference. Thirty pages will give you the working method; the rest gives you the depth. Chapter 4 of the book is a compressed version of Shostack’s first six chapters.
- OWASP Foundation. Threat Modeling Cheat Sheet. https://cheatsheetseries.owasp.org/cheatsheets/Threat_Modeling_Cheat_Sheet.html. One A4 page that maps onto Shostack’s Four Questions — keep it open during the STRIDE pass.
- Threat Modeling Manifesto Working Group. Threat Modeling Manifesto (2020). https://www.threatmodelingmanifesto.org/. Eight hundred words, co-signed by fifteen of the field’s leading practitioners; the closest thing the discipline has to a shared statement of values.
- The canonical answer key. Once you have submitted, read
apps/galway-notes/docs/threat-model.mdend-to-end. Every threat you identified maps to a deliberate vulnerability (BUG-N) you will exploit in Week 3.