Threat Modeling

How to Threat Model a QR Code Payment System (Use Case 1)

A step-by-step guide to identifying trust boundaries, attack surfaces, and defense-in-depth strategies for a real-world payment system.

Collaborating with Claude


Threat modeling is one of the most valuable skills a security engineer can have. It’s not about memorizing vulnerability lists — it’s about systematically thinking through how a system can be attacked and building layered defenses.

In this post, I’ll walk through a complete threat model of a QR code payment system — the kind of system companies like Meta, PayPal, or Square might build. We’ll cover the full framework: system decomposition, trust boundaries, attack surfaces, priority threats, and defense-in-depth mitigations.

The goal of threat modeling isn’t to find every possible vulnerability. It’s to understand what you’re protecting, who might attack it, and where your defenses are weakest.


The System: QR Code Payments

Here’s the system we’re modeling: a user opens a mobile app, generates a QR code linked to their payment account, and a merchant scans it to charge them. Simple in concept, complex in security.

System Components:

👤 User (Mobile App) → 🖥️ Token Server (QR Generator) → 🔔 Push Service (Confirmation) → 🏪 Merchant (POS Terminal) → 💳 Payment Processor

The payment process has four distinct phases:

Phase 1: 🔑 Token Generation

  • User opens app (authenticated session)
  • App requests QR code from server
  • Server generates one-time token using CSPRNG
  • Token bound to user_id and device fingerprint
  • QR code returned with 90-second TTL

Phase 2: 📱 Merchant Scan

  • User displays QR code on screen
  • Merchant scans with POS terminal
  • Token and merchant_id sent to server
  • Server validates: not expired, not used
  • Token bound to this specific merchant

Phase 3: ✅ User Confirmation

  • Merchant enters charge amount
  • Server sends push notification to user
  • User sees: “Store X wants to charge $25.00”
  • User confirms or rejects
  • No response within timeout → auto-reject

Phase 4: 💰 Payment Execution

  • If confirmed → payment processor executes charge
  • If rejected or timeout → transaction cancelled
  • Token invalidated permanently in all cases
  • Receipt sent to both user and merchant
  • Transaction logged for audit

Step 1: Define Trust Boundaries

Trust boundaries are the most important concept in threat modeling. A trust boundary is where the level of trust changes — where data moves from a zone you control to one you don’t (or vice versa). Attacks almost always happen at boundary crossings.

Our system has four distinct trust zones:

🔴 UNTRUSTED — User Device

We have zero control. Could be rooted, running malware, or capturing screenshots. All data from this zone must be validated.

🟡 SEMI-TRUSTED — Merchant

KYC-verified and using our SDK, but we don’t control their hardware. They could tamper with the SDK or attempt replay attacks.

🟢 TRUSTED — Our Servers

Token server, auth, fraud engine, push service, audit logs. We control this environment. Defense in depth still applies internally.

🟣 EXTERNAL TRUSTED — Payment Processor

Third-party (Stripe, Adyen). Trusted via contract and mTLS, but we can’t control their internal security posture.

💡 Key Insight: Every arrow in the architecture diagram that crosses a trust boundary is a potential attack surface. Focus your security analysis on the transitions between trust zones, not just on individual components.

Boundary Crossings: Where Attacks Happen

There are three critical boundary crossings in this system. Each one represents a point where data moves between trust zones and must be validated, authenticated, and protected.

USER ↔ SERVER (Steps ①②⑦) User device sends/receives sensitive data across untrusted network to our servers.

  • Threats: MITM Session theft Token interception
  • Defenses: TLS 1.3 Cert pinning Device binding

MERCHANT ↔ SERVER (Steps ④⑨) Merchant POS sends scanned token and receives transaction results through semi-trusted channel.

  • Threats: Replay attack SDK tampering Amount manipulation
  • Defenses: Mutual TLS SDK integrity Transaction binding

SERVER ↔ PAYMENT (Step ⑧) Our server sends payment execution requests to external processor with user’s financial details.

  • Threats: Double charge Amount mismatch Processor outage
  • Defenses: Mutual TLS Idempotency keys Amount re-verify

Step 2: Identify Assets

Before listing threats, we need to know what we’re protecting. Assets aren’t just data — they’re anything of value that an attacker might target.

  1. User’s money — the ultimate target. Every attack eventually aims here.
  2. QR code token — a reference to the user’s payment account. If stolen or predicted, an attacker can initiate unauthorized charges.
  3. Token generation system — the server that creates QR codes. If compromised, an attacker can generate tokens for any user.
  4. Push notification channel — the user’s only chance to reject a charge. If suppressed or spoofed, users lose their safety net.
  5. Transaction data — payment history, amounts, merchant relationships. Privacy-sensitive.
  6. Merchant credentials — SDK keys that authenticate merchants. If stolen, attackers can impersonate legitimate merchants.
  7. User session — if hijacked, the attacker can generate QR codes as the victim.

Step 3: Enumerate Threats

Now we systematically identify threats at each component and boundary crossing, focusing on the most impactful scenarios.

ThreatSeverityAttack VectorImpact
Token Server Compromise🔴 P0 CRITICALAttacker gains access to token generation systemGenerate tokens for any user → mass financial loss
QR Code Replay🔴 P0 CRITICALMerchant captures token, replays for multiple chargesUser charged multiple times for single transaction
Push Notification Compromise🟠 P1 HIGHSuppress, spoof, or flood notificationsUnauthorized charges go undetected
Token Prediction🔴 P0 CRITICALWeak RNG allows predicting future tokensForge tokens without server access
QR Code Interception🟠 P1 HIGHScreen capture, shoulder surfing, malicious overlayStolen token used at different merchant
Amount Tampering🟠 P1 HIGHMalicious merchant modifies charge amountUser charged more than approved
Notification Fatigue🟡 P2 MEDIUMFlood user with small charge requestsUser approves without reading

Step 4: Token Binding — The Core Defense

The most important security mechanism in this system is token binding. Rather than creating a generic token that works anywhere, we progressively bind the token to specific attributes at each phase.

json

// Server-side token object — bound progressively
{
  "token":              "qr_7f3a9b2c...",        // CSPRNG generated
  "user_id":            "user_456",              // bound at generation
  "device_fingerprint": "d8a9f2e1...",           // bound at generation
  "merchant_id":        "merchant_789",          // bound at scan
  "amount":             25.00,                   // set by merchant, confirmed by user
  "created_at":         "2026-02-27T14:30:00Z",
  "expires_at":         "2026-02-27T14:31:30Z",  // 90 second TTL
  "status":             "pending",               // → used → expired
  "single_use":         true                     // invalidated after first use
}

With full transaction binding, even if a token is intercepted, the attacker can’t use it at a different merchant, can’t change the amount, and can’t replay it after it’s been used. Each constraint eliminates an entire class of attacks.


Step 5: Defense in Depth

No single security control is sufficient. If one layer fails, the next layer should catch the attack. Here’s how defenses are layered across the system:

🔐 Token Security CSPRNG for unpredictable generation. One-time use. 90-second TTL. Server-side invalidation on any outcome — confirm, reject, or timeout.

🧱 Transaction Binding Token progressively locked to user_id, device fingerprint, merchant_id, and amount. Prevents cross-merchant replay and amount tampering.

👤 User Confirmation Explicit approve/reject for every charge. In-app verification, not just push notification. Auto-reject on timeout. Biometric for high amounts.

📊 Rate Limiting Per-user, per-merchant, per-token rate limits. Prevents replay attempts and notification flooding. Anomaly detection on patterns.

🔍 Fraud Detection Real-time scoring on transaction patterns. Flag unusual amounts, frequencies, locations, or merchant behaviors. Automated holds on suspicious activity.

🌐 Transport Security TLS 1.3 for all communications. Certificate pinning on mobile app. Mutual TLS for merchant SDK and payment processor connections.

Defense in Depth in Action: Consider a replay attack: the one-time token stops it at layer 1. If that somehow fails, the TTL stops it at layer 2. If both fail, the user confirmation catches it at layer 3. If the user isn’t paying attention, rate limiting blocks rapid replays at layer 4. And the fraud engine catches any anomalous patterns at layer 5.

Five independent layers must all fail for the attack to succeed. That’s defense in depth.


Step 6: The Push Notification Blind Spot

One often-overlooked attack surface is the push notification channel. The user confirmation is the last line of defense before payment execution. If an attacker can compromise this channel, the entire security model weakens.

Three specific attacks target this channel:

⚠ Notification Suppression

If an attacker blocks the push notification from reaching the user, the user never sees the charge request. The system must never auto-approve on timeout — silence must always mean rejection.

⚠ Notification Spoofing

On a compromised device, a malicious app could fake the notification UI to show “$5.00” when the actual charge is “$500.00.” The confirmation must happen inside the app, not just through the notification, with the amount fetched directly from the server.

⚠ Notification Flooding

Sending dozens of small charge requests causes notification fatigue — users start approving without reading. Rate limiting per user prevents this, capping how many pending charges can exist simultaneously.


The Threat Modeling Framework

The specific QR code system is just an example. The framework is universal. You can apply this same process to any system:

  1. Decompose the system — identify all components and how data flows between them.
  2. Draw trust boundaries — determine what you control, partially control, and don’t control at all.
  3. Identify assets — what are you protecting? Follow the money, the data, and the trust.
  4. Enumerate threats — at every boundary crossing and for every asset, ask: who would attack this, how, and why?
  5. Prioritize — not all threats are equal. Focus on the most likely and most impactful.
  6. Design mitigations — layer your defenses so no single failure is catastrophic.

Security isn’t about building a perfect wall. It’s about building enough walls that an attacker runs out of resources before they run out of walls.


Key Takeaways

📌 Remember These Principles:

  • Trust boundaries are where attacks happen. Focus your security analysis on boundary crossings, not just individual components.
  • Token binding eliminates entire attack classes. The more attributes you bind to a token, the narrower the attacker’s options become.
  • Never auto-approve on silence. In payment systems, timeout must always mean rejection.
  • Defense in depth is not optional. Every control will fail eventually. The question is whether your next layer catches it.
  • Think like an attacker, build like an engineer. Creative offense informs disciplined defense.

Written as part of a security engineering interview preparation series. Feedback and discussions welcome.