Attack Trees and Abuse Cases for Threat Modeling

Attack Trees and Abuse Cases for Threat Modeling - article cover image Security
Time it takes to read this article 7 minutes.

Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.

Introduction

An attack tree is a structured, hierarchical model of how an adversary could achieve a specific goal against a system. The root node is the attacker’s objective — “exfiltrate customer payment data”, “take over an admin account” — and child nodes decompose that goal into the concrete sub-goals and techniques that could achieve it, combined with AND/OR logic. An abuse case (or misuse case) is the complementary, narrative form of the same idea: instead of a normal use-case description of what a user does with the system, it describes what a malicious actor does *to* the system, and which legitimate control is supposed to stop them.

Both techniques exist to answer a question that a plain feature list cannot: not “what does the system do”, but “what can go wrong, and how, and is there a control for it.” They are cheap — a whiteboard and a spreadsheet — and they force the same rigor a penetration test would apply, but earlier in the SDLC, before a line of code is written. Used well, an attack tree becomes a living artifact: new branches get added as the architecture changes, and each leaf can be tagged with whether a mitigating control exists, is planned, or is missing, which turns the diagram directly into a backlog.

Attack Prerequisites

Building an attack tree or abuse-case set that is actually useful, rather than a box-ticking diagram nobody reads, requires a few inputs to be in place first:

  • A defined asset and goal — you cannot build a tree without knowing what the attacker is after (a database, a signing key, an admin session); trees without a root goal sprawl into unusable generality.
  • Architecture knowledge — data flow diagrams, trust boundaries, and authentication/authorization points, so branches reflect the real system rather than a generic OWASP checklist.
  • Participation from people who know the attacker’s side — a pentester, red teamer, or at minimum someone who reads incident write-ups, so branches reflect realistic technique rather than only textbook categories.
  • A way to track disposition per leaf — mitigated, accepted, unmitigated — otherwise the tree is a one-time exercise instead of a maintained control map.

How It Works

An attack tree decomposes a goal recursively. The root is the attacker’s objective; each child node is a way to achieve the parent, and children are marked OR (any one sub-node suffices) or AND (all sub-nodes are required together). Leaf nodes are concrete, testable actions: “obtain a valid session cookie via XSS”, “guess a weak admin password”, “exploit an unpatched CVE on the API gateway”. Each leaf can optionally carry attributes — cost to the attacker, required skill, likelihood, and existing countermeasure — which lets a team prioritize defenses on the branches that are cheap for an attacker and currently uncovered, rather than spreading effort evenly.

Abuse cases work from the opposite direction and are better suited to requirements and story-level threat modeling. A use case (“user resets their password via email link”) gets a paired abuse case (“attacker requests a reset for a victim’s email and races them to the link”, or “attacker enumerates valid accounts via differing reset responses”). Each abuse case names the actor, the malicious goal, the abused feature, and the mitigating requirement that should be added to the user story — this is what makes abuse cases so effective inside agile teams: they slot directly into the same backlog as functional stories, with the same definition of done.

The two techniques compose well. Attack trees are best for a whole system or high-value asset (an architecture-level exercise, often done once per major release or design change), while abuse cases are best attached to individual features as they are written, because they are lightweight enough to produce per sprint. Many teams run STRIDE or PASTA at the architecture level to find the branches, then hand individual branches to feature teams as abuse cases to refine and close.

Practical Example / Configuration

A minimal attack tree for the goal “exfiltrate customer payment data”, expressed as a table so it can live in a ticketing system rather than only a diagram tool:

GOAL: Exfiltrate stored customer payment data (root, OR)
├─ [OR] Compromise the database directly
│   ├─ [AND] Obtain DB credentials (phishing, leaked .env, weak secret) 
│   │        + Network path to DB (flat VPC / missing security group)
│   └─ [OR] SQL injection in an unauthenticated endpoint
├─ [OR] Compromise an application identity with data access
│   ├─ [AND] Steal an admin session (stored XSS) + no session binding
│   └─ [AND] IDOR on /api/orders/{id} + missing object-level authz
├─ [OR] Compromise a backup or export artifact
│   └─ [AND] Public S3 bucket/misconfigured ACL + unencrypted backup
└─ [OR] Compromise a third-party integration with data access
    └─ [AND] Leaked API key in source control + overly broad key scope
TEXT

The same content as abuse cases, tagged for backlog tracking:

  • Actor: unauthenticated external attacker · Abuse case: enumerate order IDs sequentially on GET /api/orders/{id} to read other customers’ payment details · Abused feature: order lookup · Mitigation: enforce object-level authorization (verify order.user_id == session.user_id) · Status: unmitigated — P1.
  • Actor: authenticated low-privilege user · Abuse case: replay a captured session cookie from a different device after a stored-XSS payload harvests it · Abused feature: comment field, session handling · Mitigation: output encoding on render + HttpOnly/SameSite cookies + session binding to device fingerprint · Status: partially mitigated.
  • Actor: insider with backup-export role · Abuse case: export an unencrypted backup to a personal cloud bucket · Abused feature: backup export job · Mitigation: encrypt exports at rest with a key the export role cannot read, log and alert on export destinations outside the allowlist · Status: mitigated.

Walkthrough / Implementation

Running a first attack-tree workshop end to end typically follows a fixed sequence. Start by fixing scope: pick one high-value asset rather than the whole system, otherwise the exercise never converges.

1. Define the root goal in attacker terms (not "secure the app", but
   "steal payment data" / "achieve remote code execution on the API host").
2. Draw a data-flow diagram and mark trust boundaries (browser -> LB -> app
   -> DB, third-party webhooks, admin plane).
3. Brainstorm first-level branches: what are the 3-6 fundamentally different
   ways to reach the goal (direct DB compromise, application logic abuse,
   compromised identity, supply chain, physical/insider)?
4. Decompose each branch into AND/OR sub-nodes until leaves are concrete,
   testable actions - if a leaf can't be turned into a pentest task or a
   Semgrep/DAST check, it's still too abstract.
5. Tag each leaf: existing control, cost/skill for the attacker, likelihood.
6. Convert unmitigated, low-cost-for-attacker leaves into backlog tickets.
TEXT

For feature-level work, pair each user story with its abuse case at grooming time rather than after the fact:

User story: "As a customer, I can download my invoice as a PDF."
Abuse case: "As an attacker, I can change the invoice ID in the download
  URL to fetch another customer's invoice (IDOR)."
Added acceptance criterion: "The endpoint verifies invoice.customer_id ==
  session.customer_id and returns 403 otherwise; covered by a negative test."
TEXT

Note: Attack trees decay fast if the architecture changes and the tree does not. Treat the tree as a living document tied to the design doc, and re-run the branch review whenever a new trust boundary is introduced (a new third-party integration, a new admin surface), not on a fixed annual schedule.

Note: Do not confuse an attack tree with a fault tree from safety engineering — fault trees model accidental failures with probabilities; attack trees model an intelligent, adaptive adversary who will route around whichever branch you harden, which is why prioritizing the *cheapest unmitigated* leaf matters more than the *statistically likeliest* one.

Detection and Defense

Attack trees and abuse cases are themselves a prevention control, but they need process hooks to stay effective rather than becoming shelf-ware:

  • Attach abuse cases to the definition of done for any story that touches authentication, authorization, payment, or file/data export — no story is complete without at least one abuse case reviewed.
  • Re-run the architecture-level tree at design-review time for any new trust boundary, and store it alongside the ADR / design doc, not in a separate forgotten wiki page.
  • Trace leaves to tests — an unmitigated leaf should map to a test case (unit, integration, or a DAST/pentest scenario) that fails until the mitigation lands, giving the tree an automated proof of closure.
  • Feed real incident and pentest findings back into the tree as new branches, so it reflects observed attacker behavior, not only theoretical categories.
  • Score leaves by attacker cost, not just impact — a low-impact but trivially cheap leaf (a default credential) often deserves fixing before a high-impact but expensive one (a zero-day), because it will be found first.

Real-World Impact

Attack trees originated in Bruce Schneier’s 1999 writing on modeling adversary behavior and were later formalized in threat-modeling methodologies such as Microsoft’s STRIDE and OWASP’s PASTA; abuse cases have an equally long pedigree in requirements-engineering literature (Sindre and Opdahl). Both remain in active use today precisely because they require no special tooling to start — a whiteboard, index cards, or a spreadsheet is enough — and because security findings phrased as abuse cases translate directly into acceptance criteria that a product team already knows how to act on, closing the classic gap between a security team’s finding and an engineering team’s backlog.

Conclusion

Attack trees and abuse cases turn “we should think about security” into a concrete, prioritized list of attacker-shaped work items. The value is not in the diagram itself but in the discipline it enforces: name the goal, enumerate the realistic ways to reach it, and track which ones are actually covered. Run them early, keep them current as the architecture evolves, and route every unmitigated leaf into the same backlog as everything else the team ships.

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL