A04:2021 – Insecure Design - Attack Vectors
Educational purpose only. This page describes how design-level weaknesses are abused, at a conceptual level, so that developers and defenders can recognize and eliminate them. It contains no weaponized exploit code. The request examples are illustrative of legitimate-looking traffic that produces illegitimate outcomes.
Table of Contents
- The Core Attack Flow
- Design-Flaw Attack Patterns
- 1. Workflow Step-Skipping
- 2. Trusting Client-Supplied Values
- 3. Missing Anti-Automation
- 4. Small-Secret Brute Force
- 5. Negative / Boundary Quantities
- 6. Coupon and Refund Abuse
- 7. Race-Condition Workflow Abuse
- 8. Weak Recovery / Fallback Paths
- 9. Broken Trust Boundaries
- 10. Resource Enumeration by Design
- 11. Unbounded Resource Consumption
- 12. Implicit Inter-Service Trust
- Detection Techniques
- Next Steps
The Core Attack Flow
Attacking an insecure design does not look like a traditional exploit. There is no malformed payload and no crash. Instead, the attacker studies the intended behavior, then finds an unintended path through legitimate operations. The general method is remarkably consistent:
Step 1 Map the workflow Observe the happy path: which requests, in
which order, with which fields.
Step 2 Question assumptions Ask "what does the server TRUST here?"
(client price? step order? one attempt?
this field's sign? this tenant id?)
Step 3 Break the assumption Replay/modify a request that violates the
unspoken rule the design assumed users obey.
Step 4 Observe the outcome Did the server enforce the rule, or accept
the illegitimate state?
Step 5 Automate / scale If a control is missing, repeat at machine
speed to maximize impact.
The attacker's core tool is simply an intercepting proxy that lets them replay and edit HTTP requests the browser would never send. Every pattern below is a specific instance of "the server trusted something it should have verified."
Design-Flaw Attack Patterns
1. Workflow Step-Skipping (State-Transition Abuse)
The assumption: "Users go through steps in order, so by the time they hit /confirm they must have paid."
The abuse: The attacker POSTs directly to a later step, skipping the ones that enforce payment or eligibility. If the server does not re-verify that prerequisites completed, it accepts the jump.
# Intended sequence
POST /checkout/shipping
POST /checkout/payment <-- charges the card
POST /checkout/confirm
# Abuse: attacker never sends the payment step
POST /checkout/shipping
POST /checkout/confirm <-- order confirmed, never charged
Root design flaw: The order's state machine has no server-side gate asserting state == PAID before CONFIRMED. The design assumed the client's UI enforces order.
2. Trusting Client-Supplied Security Values
The assumption: "The price/role/limit shown in the form is the one the server will use."
The abuse: The attacker changes the value in the request body. Anything sent by the client can be edited.
POST /cart/add
{ "sku": "LAPTOP-15", "price": 1299.00, "qty": 1 }
# Attacker edits the price the client "helpfully" submitted:
POST /cart/add
{ "sku": "LAPTOP-15", "price": 1.00, "qty": 1 }
Root design flaw: Price is authoritative on the client. A secure design never accepts price from the client at all — it looks the price up server-side from the SKU.
3. Missing Anti-Automation (No Rate Limiting)
The assumption: "One person tries to log in a few times."
The abuse: The attacker scripts thousands of attempts — credential stuffing, password spraying, ID enumeration, or scraping — because nothing throttles them.
for cred in leaked_credentials: # millions of pairs
POST /login {user, pass} # no lockout, no throttle,
# no CAPTCHA, no bot check
Root design flaw: The workflow was designed to authenticate one honest user, not to resist automation. Rate limiting, lockouts, and bot defenses were never part of the design (CWE-799: improper control of interaction frequency).
4. Small-Secret Brute Force (OTP / Reset Codes)
The assumption: "A 6-digit code is secret."
The abuse: A 6-digit code has only 1,000,000 possibilities. With no attempt cap and unlimited re-requests, the entire space is guessable.
POST /verify-otp { "phone": "...", "code": "000000" }
POST /verify-otp { "phone": "...", "code": "000001" }
... # exhaust the space
Root design flaw: A small secret space paired with unlimited guessing. The fix is a design decision: cap attempts, expire fast, lock after N failures, and/or enlarge the secret space.
5. Negative and Boundary Quantities
The assumption: "Quantities and amounts are positive."
The abuse: A negative quantity or amount inverts the arithmetic — a refund becomes a charge to the attacker's favor, or a total goes negative and issues store credit.
POST /transfer { "to": "attacker", "amount": -500 }
# If interpreted naively: pulls 500 FROM the recipient TO the attacker.
POST /cart { "sku": "GIFTCARD", "qty": -3 }
# Order total drops by 3x the price; attacker "owed" money.
Root design flaw: The domain rules ("amount > 0", "qty in 1..maxStock") were never expressed as server-side invariants.
6. Coupon, Promotion, and Refund Abuse
The assumption: "A coupon is used once; a refund matches a real return."
The abuse: Apply the same single-use coupon repeatedly (parallel requests or replaying the apply call), stack mutually-exclusive promotions, or request refunds for items never returned.
POST /cart/apply-coupon { "code": "SAVE50" } x N in parallel
# Discount applied N times because "already used?" is checked
# per-request, not atomically reserved.
Root design flaw: Promotion redemption and refund eligibility are not modeled as authoritative, atomic, auditable state transitions with enforced uniqueness.
7. Race-Condition Workflow Abuse (TOCTOU)
The assumption: "Requests happen one at a time, so a check-then-act is safe."
The abuse: The attacker fires many concurrent requests in the tiny window between the check ("do you still have balance / stock / one free trial?") and the act ("deduct it"). Each request sees the pre-deduction state and all succeed.
Thread A: check balance=100 ----\
Thread B: check balance=100 ----- both pass the check,
Thread C: check balance=100 ----/ each withdraws 100 -> 300 withdrawn
Root design flaw: The design used a non-atomic check-then-act instead of an atomic, transactional reservation (e.g., a conditional UPDATE or a unique constraint). Concurrency was never modeled as an adversarial condition.
8. Weak Recovery and Fallback Paths
The assumption: "Recovery is for legitimate users who forgot their password."
The abuse: The attacker targets the recovery path precisely because it is the weakest link — answering public "security questions," receiving codes on an unverified channel, or exploiting a fallback that downgrades to a weaker check when the strong one is unavailable.
POST /recover { "user": "victim", "securityAnswer": "Springfield" }
# Answer is the victim's publicly-known hometown.
Root design flaw: The fallback is weaker than the primary authentication it bypasses. A secure design makes recovery at least as strong as login.
9. Broken Trust Boundaries and Tenant Segregation
The assumption: "Each customer only ever sees their own tenant_id, so we can trust it."
The abuse: The attacker changes the tenant/org/account identifier in a request and reaches another tenant's data or actions, because segregation was assumed rather than enforced at the boundary.
GET /api/orgs/1024/reports # my org
GET /api/orgs/1025/reports # someone else's org -> 200 OK
Root design flaw: The system was designed as if tenants were cooperative. Authorization is not enforced as a first-class trust-boundary constraint on every cross-tenant reference (CWE-501: trust boundary violation).
10. Resource Enumeration by Design
The assumption: "Nobody will iterate our sequential IDs / discover valid usernames."
The abuse: Sequential or predictable identifiers, plus responses that distinguish "exists" from "does not exist," let an attacker enumerate users, invoices, or documents wholesale.
POST /forgot-password { "email": "a@corp.com" } -> "No such account"
POST /forgot-password { "email": "b@corp.com" } -> "Reset link sent"
# The differing responses enumerate valid accounts by design.
Root design flaw: The design leaks existence through predictable identifiers and distinguishable responses, rather than using opaque identifiers and uniform responses.
11. Unbounded Resource Consumption
The assumption: "Users request reasonable amounts of work."
The abuse: The attacker requests enormous page sizes, deeply nested queries, giant exports, or expensive report generation, exhausting CPU, memory, or cost with a single crafted request.
GET /api/search?limit=100000000&expand=all&depth=50
POST /report/generate { "range": "10years", "format": "pdf" } x100
Root design flaw: No designed limits on request cost, pagination size, concurrency, or query complexity. Resource boundaries were never modeled.
12. Implicit Inter-Service / Internal Trust
The assumption: "This request came from our internal network / gateway, so it is trustworthy."
The abuse: Once an attacker reaches the internal network (via SSRF, a compromised dependency, or a pivot), services that authenticate nothing internally hand over full access. Headers like X-Internal: true or X-User-Id are forged.
POST /internal/admin/grant
X-Internal: true
X-User-Role: admin # forged; the service trusts it blindly
Root design flaw: A "hard shell, soft interior" design where the network perimeter is the only boundary. A secure design authenticates and authorizes every call regardless of origin (zero-trust).
Detection Techniques
Because these flaws produce no crash or signature, detection is about looking for missing controls and anomalous-but-valid sequences:
| Technique | What it surfaces |
|---|---|
| Threat modeling / design review | The primary method: enumerate assets, trust boundaries, and abuse cases to find controls that were never designed. |
| Abuse-case testing | Deliberately drive the workflow off the happy path (skip steps, replay, negate, parallelize) and assert the server rejects it. |
| Request replay / fuzzing of values | Edit prices, quantities, ids, and step order in an intercepting proxy to test what the server actually trusts. |
| Concurrency testing | Fire N simultaneous requests at any one-time or limited benefit to expose TOCTOU races. |
| Rate/volume monitoring | Alert on request-frequency anomalies per account/IP/endpoint — the fingerprint of missing anti-automation. |
| Business-metric anomaly detection | Watch for impossible outcomes: negative totals, discounts exceeding price, refunds without returns, single-use codes redeemed repeatedly. |
Key insight: Every pattern above reduces to one sentence — the server trusted something it should have verified. Find those trust assumptions, and you have found the design flaws.
Next Steps
- Overview: The design-vs-implementation distinction and why this category exists.
- Prevention: Threat modeling, secure design patterns, and guardrails that close these gaps.
- Examples: Concrete vulnerable-vs-secure code for these patterns.
- Hands-On Lab: Exploit a missing-rate-limit design, then add the control.