Threat Modeling
- Threat Modeling
Finding the flaws before you build them
Threat modeling is a structured process for identifying security threats to a system, understanding their impact, and determining mitigations — during design, before code is written. It is one of the highest-leverage security activities there is: finding a design-level vulnerability during planning costs far less to fix than discovering it in production.
ℹ️ Threat modeling is where adversary models on the offense side meet the controls on the defense side: you enumerate what an attacker could do, then decide — per threat — to mitigate, accept, or transfer the risk. It feeds directly into vulnerability management and the rest of the secure SDLC.
What is Threat Modeling?
Threat modeling answers four questions (per the OWASP Threat Modeling Manifesto):
- What are we building? — understand the system through diagrams and documentation
- What can go wrong? — identify threats systematically
- What are we going to do about it? — decide on mitigations or accepted risks
- Did we do a good enough job? — validate that the model is complete and mitigations work
Threat modeling is not a one-time activity. It should be revisited when the architecture changes, new features are added, or new threat intelligence emerges.
When to threat model
- During system design (most valuable — cheapest to fix)
- During sprint planning for features that touch security boundaries
- After a security incident (retrospective modeling)
- During security reviews of third-party integrations
- As part of an annual security review cycle
STRIDE
STRIDE is the most widely used threat categorization framework, developed at Microsoft. Each letter represents a threat category:
| Letter | Threat | Violated property | Example |
|---|---|---|---|
| S | Spoofing | Authentication | Attacker impersonates a legitimate user or service |
| T | Tampering | Integrity | Attacker modifies data in transit or at rest |
| R | Repudiation | Non-repudiation | User denies performing an action; no audit log exists |
| I | Information Disclosure | Confidentiality | Sensitive data exposed via error messages or misconfiguration |
| D | Denial of Service | Availability | Service made unavailable through resource exhaustion |
| E | Elevation of Privilege | Authorization | Attacker gains higher permissions than intended |
Applying STRIDE to a component
For each component or data flow in a system diagram, ask which STRIDE threats apply:
Example: REST API endpoint POST /api/v1/transfer (bank transfer)
S — Spoofing:
Threat: Attacker forges authentication token to impersonate a user
Mitigation: Validate JWT signature; use short-lived tokens; enforce MFA for transfers
T — Tampering:
Threat: Attacker modifies transfer amount in transit
Mitigation: TLS for all traffic; HMAC on request body; idempotency keys
R — Repudiation:
Threat: User claims they did not initiate a transfer
Mitigation: Audit log with timestamp, user ID, IP, and request body hash
I — Information Disclosure:
Threat: Error messages reveal account balance or SQL query structure
Mitigation: Generic error responses; structured error codes; no stack traces in production
D — Denial of Service:
Threat: Attacker floods endpoint to exhaust transfer processing capacity
Mitigation: Rate limiting per user; CAPTCHA for high-value operations; circuit breaker
E — Elevation of Privilege:
Threat: Regular user accesses admin transfer approval endpoint
Mitigation: Role-based authorization check on every endpoint; integration test coverage
STRIDE threat modeling workflow
# Python: simple threat model data structure
from dataclasses import dataclass, field
from enum import Enum
from typing import List
class StrideCategory(Enum):
SPOOFING = "Spoofing"
TAMPERING = "Tampering"
REPUDIATION = "Repudiation"
INFORMATION_DISCLOSURE = "Information Disclosure"
DENIAL_OF_SERVICE = "Denial of Service"
ELEVATION_OF_PRIVILEGE = "Elevation of Privilege"
@dataclass
class Threat:
category: StrideCategory
component: str
description: str
mitigation: str
risk_rating: str # Critical / High / Medium / Low
status: str # Open / Mitigated / Accepted
@dataclass
class ThreatModel:
system_name: str
threats: List[Threat] = field(default_factory=list)
def add_threat(self, threat: Threat):
self.threats.append(threat)
def open_threats(self) -> List[Threat]:
return [t for t in self.threats if t.status == "Open"]
def summary(self):
by_rating = {}
for t in self.open_threats():
by_rating.setdefault(t.risk_rating, []).append(t)
for rating in ['Critical', 'High', 'Medium', 'Low']:
items = by_rating.get(rating, [])
if items:
print(f"{rating}: {len(items)} open threats")
model = ThreatModel("Payment API")
model.add_threat(Threat(
category=StrideCategory.SPOOFING,
component="POST /api/v1/transfer",
description="Attacker forges JWT to impersonate account holder",
mitigation="Validate RS256 JWT signature against JWKS endpoint; enforce exp claim",
risk_rating="Critical",
status="Open",
))
model.summary()
PASTA
PASTA (Process for Attack Simulation and Threat Analysis) is a risk-centric threat modeling methodology with seven stages. Where STRIDE is threat-centric (what can go wrong?), PASTA is attacker-centric (what would an attacker do?).
PASTA stages
| Stage | Name | Key activities |
|---|---|---|
| 1 | Define Objectives | Business and security objectives; regulatory requirements |
| 2 | Define Technical Scope | System components, dependencies, interfaces |
| 3 | Application Decomposition | Data flows, trust boundaries, entry points |
| 4 | Threat Analysis | Threat intelligence, attacker profiles, TTPs |
| 5 | Vulnerability Analysis | SAST/DAST results, CVEs in dependencies, configuration issues |
| 6 | Attack Modeling | Attack trees, attack scenarios correlated to vulnerabilities |
| 7 | Risk and Impact Analysis | Business impact quantification, residual risk, remediation priority |
Attack trees
Attack trees decompose an attacker’s goal into sub-goals, bottom-up, identifying possible paths:
Goal: Exfiltrate customer PII from the database
OR
├── Path 1: Exploit SQL injection in search endpoint
│ AND
│ ├── Find injectable parameter (automated scanner)
│ └── Craft payload to dump users table
│
├── Path 2: Compromise a developer account
│ AND
│ ├── Phish developer credentials
│ └── Use credentials to access production database via VPN
│
└── Path 3: Exploit misconfigured cloud storage
AND
├── Enumerate S3 buckets via company name guessing
└── Access publicly readable bucket containing database backup
# Python: represent an attack tree
from dataclasses import dataclass, field
from typing import List, Optional
from enum import Enum
class NodeType(Enum):
OR = "OR" # any child being true makes parent true
AND = "AND" # all children must be true
@dataclass
class AttackNode:
description: str
node_type: NodeType = NodeType.OR
likelihood: str = "Medium" # High / Medium / Low
children: List["AttackNode"] = field(default_factory=list)
def is_feasible(self) -> bool:
if not self.children:
return self.likelihood in ("High", "Medium")
if self.node_type == NodeType.OR:
return any(c.is_feasible() for c in self.children)
return all(c.is_feasible() for c in self.children)
root = AttackNode(
"Exfiltrate customer PII",
NodeType.OR,
children=[
AttackNode("SQL injection in search", NodeType.AND, children=[
AttackNode("Find injectable parameter", likelihood="High"),
AttackNode("Dump users table via UNION", likelihood="High"),
]),
AttackNode("Compromise developer account", NodeType.AND, children=[
AttackNode("Phish developer credentials", likelihood="Medium"),
AttackNode("Use credentials without MFA", likelihood="Low"),
]),
]
)
print(f"Attack feasible: {root.is_feasible()}")
Threat Modeling Workflows
Data Flow Diagrams
A Data Flow Diagram (DFD) is the standard artifact for threat modeling. It shows how data moves through a system and where trust boundaries exist.
DFD elements:
| Symbol | Represents | Threat modeling focus |
|---|---|---|
| Rectangle | External entity (user, external system) | Spoofing entry points |
| Rounded rectangle | Process | Business logic vulnerabilities |
| Open rectangle | Data store (database, file, cache) | Data at rest, access controls |
| Arrow | Data flow | Data in transit, tampering |
| Dashed line | Trust boundary | Where authentication/authorization is enforced |
# Generate DFDs with draw.io (CLI export)
# Or use the pytm library for programmatic threat modeling
pip install pytm
# pytm generates DFDs and STRIDE threat lists from Python code
# save as payment_model.py, then run:
# python3 payment_model.py --list # print STRIDE threat list
# python3 payment_model.py --dfd # generate DFD (requires graphviz)
# python3 payment_model.py --json out.json # machine-readable output
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor, TLSVersion
tm = TM("Payment API Threat Model")
tm.description = "Threat model for the payment processing API"
tm.isOrdered = True
# Boundaries
internet = Boundary("Internet")
dmz = Boundary("DMZ")
internal = Boundary("Internal Network")
# Components
user = Actor("Customer")
user.inBoundary = internet
api = Server("Payment API")
api.inBoundary = dmz
api.usesSessionTokens = True # enables session-hijacking threats
api.minTLSVersion = TLSVersion.TLSv12 # triggers outdated-TLS threats below this
database = Datastore("PostgreSQL")
database.inBoundary = internal
database.isSQL = True # enables SQL injection threat rules
database.storesSensitiveData = True # enables data-exposure threat rules
# Data flows
Dataflow(user, api, "HTTPS POST /api/v1/transfer")
Dataflow(api, database, "Parameterized SQL query")
Dataflow(database, api, "Query results")
Dataflow(api, user, "HTTPS JSON response")
tm.process() # reads sys.argv; behaviour depends on the flag passed above
Threat Modeling in the SDLC
Sprint-level threat modeling (lightweight):
For each user story touching a security boundary:
1. Draw a mini-DFD (5 minutes, on whiteboard)
2. Ask STRIDE questions for each component and data flow
3. Add security acceptance criteria to the story
4. Create security tasks for mitigations
5. Add automated test cases to verify mitigations
Design-level threat modeling (comprehensive):
1. Kick-off meeting: architect, developers, security engineer
2. System decomposition: draw DFD with all trust boundaries
3. STRIDE analysis: one pass per component and data flow
4. Risk rating: DREAD or CVSS score each threat
5. Mitigation planning: assign owners and sprints
6. Documentation: store model in the repository alongside architecture docs
7. Review trigger: model must be updated when architecture changes
Threat modeling tools
| Tool | Type | Notes |
|---|---|---|
| Microsoft Threat Modeling Tool | Desktop (Windows) | STRIDE-based; generates reports; free |
| OWASP Threat Dragon | Web / Desktop | Open-source; STRIDE; DFD editor |
| IriusRisk | SaaS | Enterprise; integrates with Jira |
| pytm | Python library | Code-based; generates DFDs and threat lists |
| Threagile | Go-based YAML | Agile threat modeling as code |
Attack Surface Mapping
The attack surface is the sum of all points where an attacker could try to enter or extract data from a system. Reducing the attack surface is one of the most effective security strategies.
Identifying the attack surface
# Network-level: discover open ports and services
nmap -sV -sC -p- --open 10.0.1.0/24
# Web application: enumerate endpoints
# Target: the DVWA instance running on ubuntu-server in the course lab (10.10.10.20)
# Using gobuster for directory/endpoint discovery
gobuster dir -u http://10.10.10.20/ -w /usr/share/wordlists/dirb/common.txt
# Using ffuf to fuzz for DVWA paths
ffuf -u http://10.10.10.20/dvwa/vulnerabilities/FUZZ -w /usr/share/wordlists/dirb/common.txt
# Cloud: enumerate public S3 buckets
python3 -c "
import boto3
s3 = boto3.client('s3', region_name='us-east-1')
for bucket in s3.list_buckets()['Buckets']:
try:
acl = s3.get_bucket_acl(Bucket=bucket['Name'])
for grant in acl['Grants']:
if grant['Grantee'].get('URI','').endswith('AllUsers'):
print(f\"PUBLIC: {bucket['Name']}\")
except Exception as e:
pass # bucket not accessible or error
"
# Kubernetes: enumerate exposed services
kubectl get services -A | grep -v ClusterIP # find NodePort and LoadBalancer services
kubectl get ingress -A # find externally accessible ingresses
Attack surface reduction
# Disable unnecessary services
systemctl list-units --type=service --state=running # audit running services
systemctl disable --now cups bluetooth avahi-daemon # disable unneeded services
# Remove unnecessary packages
apt list --installed 2>/dev/null | grep -v automatic # review installed packages
apt purge telnet ftp rsh-client rsh-server # remove legacy insecure tools
# Restrict network access
# Allow only what is needed; deny everything else
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
Risk Quantification
Not all threats are equally important. Risk quantification helps prioritize which threats to address first.
DREAD scoring
DREAD is a risk rating model with five factors (each scored 1–3):
| Factor | 1 (Low) | 2 (Medium) | 3 (High) |
|---|---|---|---|
| Damage | Minor information leak | Non-sensitive data loss | Complete system compromise |
| Reproducibility | Requires special conditions | Reproducible occasionally | Always reproducible |
| Exploitability | Requires expert attacker | Some skill required | Script kiddie / automated |
| Affected users | Single user | Group of users | All users |
| Discoverability | Requires source access | Published tool/technique | Widely known |
from dataclasses import dataclass
@dataclass
class DREADScore:
damage: int # 1-3
reproducibility: int
exploitability: int
affected_users: int
discoverability: int
def total(self) -> int:
return self.damage + self.reproducibility + self.exploitability \
+ self.affected_users + self.discoverability
def rating(self) -> str:
score = self.total()
if score >= 12:
return "Critical"
if score >= 8:
return "High"
if score >= 5:
return "Medium"
return "Low"
sqli = DREADScore(damage=3, reproducibility=3, exploitability=2,
affected_users=3, discoverability=2)
print(f"SQL injection: {sqli.total()}/15 — {sqli.rating()}")
# SQL injection: 13/15 — Critical
CVSS (Common Vulnerability Scoring System)
CVSS v3.1 remains the most widely used vulnerability scoring system in CVE databases (NVD, vendor advisories). CVSS v4.0 was published in late 2023 and is now appearing alongside v3.1, so expect to see both scores on newer CVEs:
# Parse CVSS scores from Trivy JSON output
import json
import subprocess
result = subprocess.run(
['trivy', 'image', '--format', 'json', 'nginx:latest'],
capture_output=True, text=True
)
data = json.loads(result.stdout)
critical_vulns = []
for result in data.get('Results', []):
for vuln in result.get('Vulnerabilities', []):
score = vuln.get('CVSS', {}).get('nvd', {}).get('V3Score', 0)
if score >= 9.0:
critical_vulns.append({
'id': vuln['VulnerabilityID'],
'package': vuln['PkgName'],
'score': score,
'description': vuln.get('Description', '')[:100],
})
for v in sorted(critical_vulns, key=lambda x: x['score'], reverse=True):
print(f"{v['score']:.1f} {v['id']} ({v['package']}): {v['description']}")
Key takeaways
- Threat modeling answers four questions — what are we building, what can go wrong, what do we do about it, and did we do a good enough job? — and it pays off most when done during design, before code exists.
- STRIDE is the workhorse: for each component and data flow, ask whether Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, or Elevation of privilege applies — each maps to a violated security property.
- PASTA is the risk-/attacker-centric alternative (seven stages); attack trees decompose a goal into AND/OR sub-goals so you can see every path to it.
- The Data Flow Diagram with explicit trust boundaries is the core artifact —
pytmlets you express it as code and generate the DFD and STRIDE list automatically. - Not all threats are equal: rate them with DREAD or CVSS so you fix the critical ones first, and reduce attack surface (disable services, restrict network access) as a force multiplier.
- A threat model is living documentation — store it next to the architecture docs and update it whenever the design changes.
References
- OWASP — Threat Modeling. https://owasp.org/www-community/Threat_Modeling
- OWASP — Threat Modeling Manifesto. https://www.threatmodelingmanifesto.org/
- Microsoft — STRIDE threat categories. https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats
- UcedaVélez & Morana — Risk Centric Threat Modeling (PASTA), Wiley. https://www.wiley.com/en-us/Risk+Centric+Threat+Modeling%3A+Process+for+Attack+Simulation+and+Threat+Analysis-p-9780470500965
- OWASP pytm — threat modeling as code. https://github.com/OWASP/pytm
- OWASP Threat Dragon — open-source DFD/threat editor. https://owasp.org/www-project-threat-dragon/
- FIRST — CVSS v3.1 specification. https://www.first.org/cvss/specification-document
Related course pages: DevSecOps Fundamentals · Vulnerability Management · Incident Response
🛠️ Maintenance note: scoring systems move — DREAD has fallen out of favor at Microsoft, so treat it as illustrative rather than current best practice, and watch CVSS v4.0 adoption grow relative to v3.1. Verify the
pytmAPI (class names likeServer/Datastore/Boundary) against the installed version, and re-check the Microsoft Threat Modeling Tool link each term.