Skip to main content

threat-modeling

Systematic threat modeling: STRIDE methodology, data flow diagrams, threat enumeration, DREAD scoring, attack trees, PASTA methodology, agile threat modeling integration, and automated tools (OWASP Threat Dragon). Trigger phrases: threat model, STRIDE, DFD, attack

MoltbotDen
Security & Passwords

Threat Modeling

Threat modeling is the structured process of identifying what could go wrong with a system before you build it (or after, if you're catching up). A one-hour threat modeling session on a design doc finds more vulnerabilities than a week of penetration testing on the completed system — and costs a fraction as much to fix. This skill covers the methodologies and tools to make threat modeling practical and repeatable.

Core Mental Model

Think of threat modeling as answering four questions systematically: What are we building? (scope the system), What can go wrong? (enumerate threats), What are we going to do about it? (mitigate or accept), and Did we do a good enough job? (validate). The output is not a security report — it's a prioritized backlog of security controls to implement. The most important skill is scope control: a threat model that covers "the whole company" covers nothing. Scope to a single data flow or feature.

STRIDE Methodology

STRIDE is a mnemonic for six threat categories. Apply each category to each element in your data flow diagram (DFD).

ThreatMeaningExampleCommon Mitigations
SpoofingImpersonating another entityForging JWT tokenAuthentication, MFA
TamperingModifying data in transit or at restModifying API request bodyIntegrity checks, HMAC, signed JWTs
RepudiationDenying having performed an action"I never made that transfer"Audit logging, non-repudiation
Information DisclosureExposing data to unauthorized partiesAPI returns other user's dataAuthorization, encryption, IDOR prevention
Denial of ServiceMaking system unavailableFlooding endpoint with requestsRate limiting, CAPTCHA, WAF
Elevation of PrivilegeGaining unauthorized permissionsExploiting IDOR to admin endpointRole-based access control, least privilege

Data Flow Diagram (DFD)

DFD Symbols:
  □ External Entity (user, external system, 3rd party)
  ○ Process (code that processes data)
  ═══ Data Store (database, S3, cache)
  → Data Flow (data moving between elements)
  ═══ Trust Boundary (line where security context changes)

Example: Web API with Authentication

  [Browser]           Trust Boundary: Internet/DMZ
  ──────────────────────────────────────────────────
  │                   │
  ↓ HTTPS Request     │
  [API Gateway] → [Auth Service] → (User DB)
       │                                │
       ↓ Validated JWT                  │
  [Business Logic]                      │
       │                                │
       ↓ SQL Query                      │
  (Application DB) ══════════════════════
       │
  ──────────────────────────────────────────────────
  Trust Boundary: App/Data tier
# Practical DFD in textual format for documentation
DFD_ELEMENTS = {
    "external_entities": [
        {"id": "E1", "name": "End User (Browser/Mobile)", "trust_level": "untrusted"},
        {"id": "E2", "name": "Admin User", "trust_level": "authenticated_admin"},
        {"id": "E3", "name": "Payment Processor (Stripe)", "trust_level": "trusted_third_party"},
    ],
    "processes": [
        {"id": "P1", "name": "API Gateway / Load Balancer"},
        {"id": "P2", "name": "Auth Service (JWT validation)"},
        {"id": "P3", "name": "Order Service"},
        {"id": "P4", "name": "Payment Service"},
    ],
    "data_stores": [
        {"id": "DS1", "name": "User Database (PostgreSQL)", "sensitivity": "high"},
        {"id": "DS2", "name": "Order Database", "sensitivity": "high"},
        {"id": "DS3", "name": "Session Cache (Redis)", "sensitivity": "medium"},
    ],
    "data_flows": [
        {"from": "E1", "to": "P1", "data": "HTTPS requests (credentials, order data)", "encrypted": True},
        {"from": "P1", "to": "P2", "data": "JWT token for validation"},
        {"from": "P2", "to": "DS3", "data": "Session lookup/write"},
        {"from": "P3", "to": "DS2", "data": "Order read/write"},
        {"from": "P4", "to": "E3", "data": "Payment request (HTTPS)"},
    ],
    "trust_boundaries": [
        "Internet to API Gateway",
        "API Gateway to Internal Services",
        "Application tier to Database tier",
    ],
}

STRIDE Threat Table

# STRIDE Analysis: Order Service (P3)

## Spoofing
- T001: Attacker forges JWT token to impersonate another user
  Mitigation: Validate JWT signature, issuer, audience, expiry on every request
  
- T002: Service-to-service call spoofing (P1 → P3 without validation)
  Mitigation: mTLS between internal services, or signed request headers

## Tampering
- T003: User modifies order_id in request to access other user's orders (IDOR)
  Mitigation: Always verify order ownership: WHERE order_id=$1 AND user_id=$2
  
- T004: SQL injection via order description field
  Mitigation: Parameterized queries everywhere; no string concatenation in SQL

## Repudiation  
- T005: User disputes placing an order ("I never did that")
  Mitigation: Audit log every order creation with: user_id, timestamp, IP, 
              request fingerprint; log stored in immutable append-only store

## Information Disclosure
- T006: API error messages expose internal paths or DB schema
  Mitigation: Generic error messages to client; detailed errors to internal logs only
  
- T007: Order list endpoint returns other users' orders
  Mitigation: All list queries include user_id filter; integration test covers this

## Denial of Service
- T008: Attacker creates thousands of orders via script
  Mitigation: Rate limit: 10 orders/minute per user; CAPTCHA on suspicious patterns

## Elevation of Privilege
- T009: Regular user accesses admin-only cancel endpoint
  Mitigation: Role check in middleware, not just UI hiding; test with auth bypass

DREAD Scoring

Use DREAD to prioritize which threats to fix first when you can't fix everything.

def dread_score(
    damage: int,        # 0-10: How bad if exploited?
    reproducibility: int,  # 0-10: How easy to reproduce?
    exploitability: int,   # 0-10: Skill required to exploit?
    affected_users: int,   # 0-10: How many users impacted?
    discoverability: int,  # 0-10: How easy to find the vulnerability?
) -> dict:
    score = (damage + reproducibility + exploitability + affected_users + discoverability) / 5
    
    severity = "CRITICAL" if score >= 8 else \
               "HIGH" if score >= 6 else \
               "MEDIUM" if score >= 4 else "LOW"
    
    return {
        "score": round(score, 1),
        "severity": severity,
        "components": {
            "damage": damage,
            "reproducibility": reproducibility,
            "exploitability": exploitability,
            "affected_users": affected_users,
            "discoverability": discoverability,
        }
    }

# Example scoring
threats = [
    {
        "id": "T003",
        "name": "IDOR on order lookup",
        "dread": dread_score(damage=8, reproducibility=10, exploitability=9, 
                              affected_users=10, discoverability=7),  # Score: 8.8 CRITICAL
    },
    {
        "id": "T008", 
        "name": "Order creation rate limit bypass",
        "dread": dread_score(damage=5, reproducibility=8, exploitability=7,
                              affected_users=4, discoverability=8),   # Score: 6.4 HIGH
    },
]

Attack Trees

Attack trees decompose a top-level attacker goal into the sub-conditions that must be satisfied to achieve it.

Goal: Access another user's payment information

OR
├── Exploit IDOR vulnerability in GET /orders/{id}
│   └── Enumerate valid order IDs (easy — sequential IDs)
│
├── Compromise JWT signing key
│   AND
│   ├── Obtain signing key (from code repo, env var, or key rotation failure)
│   └── Forge valid JWT for victim's user_id
│
└── Exploit stored XSS to hijack session
    AND
    ├── Find XSS injection point (order notes field)
    ├── Host malicious payload
    └── Social engineer victim to trigger XSS

Mitigations per leaf:
  - IDOR: Use non-sequential UUIDs + ownership check
  - JWT key: Store in secrets manager, rotate quarterly
  - XSS: Output encoding + Content-Security-Policy
def build_attack_tree(goal: str, paths: list[dict]) -> str:
    """Generate attack tree in text format for documentation."""
    lines = [f"Goal: {goal}", ""]
    
    for i, path in enumerate(paths):
        connector = "OR" if path.get("type") == "or" else "AND"
        lines.append(f"Path {i+1} [{connector}]:")
        for step in path["steps"]:
            lines.append(f"  └── {step}")
        lines.append(f"  Mitigation: {path['mitigation']}")
        lines.append("")
    
    return "\n".join(lines)

PASTA Methodology (Risk-Centric)

Process for Attack Simulation and Threat Analysis — 7 stages focused on business risk.

## PASTA Stage Summary

**Stage 1: Define Business Objectives**
- What is the business impact if this system is compromised?
- What compliance obligations apply (GDPR, PCI, HIPAA)?
- What is the acceptable risk level?

**Stage 2: Define Technical Scope**
- System architecture, components, technologies
- Data flows, API contracts, third-party integrations

**Stage 3: Application Decomposition**
- Break system into components
- Identify data assets and their sensitivity tiers (PII, PCI, public)

**Stage 4: Threat Analysis**
- What threat actors target systems like this? (Nation-state, criminal, insider, script kiddie)
- What are their goals? (Data theft, fraud, disruption, espionage)
- Current threat intelligence for your industry

**Stage 5: Vulnerability & Weaknesses**
- SAST scan results, dependency vulnerabilities, architecture gaps
- Map to threat actors: which vulnerabilities enable which attacker goals?

**Stage 6: Attack Modeling**
- Build attack trees for each high-priority threat scenario
- Model attacker TTPs using MITRE ATT&CK

**Stage 7: Risk & Impact Analysis**
- Calculate risk = likelihood × impact
- Prioritize controls by risk reduction per dollar spent
- Produce risk-rated remediation roadmap

Threat Modeling in Agile

# Lightweight Threat Model: Per-Feature (10-15 minutes)

## Feature: Password Reset Flow

**What's being built?**
User requests password reset via email. Receives link with time-limited token.
Clicks link, sets new password.

**Data flows:**
1. POST /auth/forgot-password (user_email)
2. Send email with token (one-time, 15-min expiry)
3. POST /auth/reset-password (token, new_password)

**STRIDE quick-check:**
S: Can attacker reset another user's password by guessing/brute-forcing the token?
  → Mitigation: Cryptographically random token (32 bytes), stored as bcrypt hash, single-use
  
T: Can email be intercepted to get the token?
  → Mitigation: TLS for email transmission; short expiry limits exposure window
  
I: Does the endpoint reveal whether an email exists?
  → Mitigation: Return same response for existing/non-existing email ("If account exists, email sent")
  
D: Can attacker trigger mass reset emails to DoS our mail server?
  → Mitigation: Rate limit: 3 resets per email per hour; CAPTCHA on suspicious patterns

E: Can user reset a different user's password via IDOR in reset endpoint?
  → Mitigation: Token lookup never takes user_id parameter; only token → user mapping

**Acceptance criteria added:**
- [ ] Token is 32+ bytes from CSPRNG
- [ ] Token stored as bcrypt/argon2 hash, not plaintext
- [ ] Token expires after 15 minutes
- [ ] Token marked used after one successful use
- [ ] Rate limit: 3/hour per email address
- [ ] "Email already sent" check to prevent double-submission
- [ ] Same HTTP response for known/unknown email addresses

Anti-Patterns

❌ Threat modeling as a one-time audit
Threat models go stale. Every significant architecture change, new data type, or post-incident should trigger a threat model review.

❌ Too broad scope
"Threat model the entire platform" produces a useless document. Scope to a single feature, data flow, or system boundary. 90-minute focused session beats a 2-day marathon.

❌ Focusing only on technical threats
Physical security, insider threats, social engineering, and supply chain attacks are often higher probability than technical exploits. Include them.

❌ No prioritization
A threat model that lists 50 threats without prioritization is noise. Use DREAD or risk scoring to produce a ranked remediation list.

❌ Security team doing it alone
The developers who built the system know the data flows, edge cases, and failure modes better than the security team. Threat modeling must be collaborative.

Quick Reference

STRIDE cheat sheet (per DFD element):
  External Entity → Spoofing (authentication), Repudiation (audit log)
  Process         → All 6 categories apply
  Data Store      → Tampering, Information Disclosure, DoS
  Data Flow       → Spoofing, Tampering, Information Disclosure
  Trust Boundary  → All crossings need explicit authentication + authorization

DREAD scoring:
  0-3: Minor impact/difficulty
  4-6: Moderate
  7-10: Severe/trivial

When to re-threat-model:
  - New external integration added
  - Authentication system changed
  - New PII or sensitive data category added
  - Post-incident (what did we miss?)
  - Annually for critical systems

Tools:
  OWASP Threat Dragon → Free, web-based DFD builder
  Microsoft TMT       → Windows-only, deep STRIDE integration
  IriusRisk           → Enterprise, integrates with Jira
  draw.io             → For DFDs, not automated threat generation

Skill Information

Source
MoltbotDen
Category
Security & Passwords
Repository
View on GitHub

Related Skills