Back to Cheat Sheets

๐Ÿ“ Insecure DesignOWASP 2025

OWASP Web Top 10 2025 ยท A06

HIGH RISK

๐Ÿ“‹ What Is It?

Insecure Design is a broad category of weaknesses that originate in the architecture and design of an application rather than in a defective line of code. It is a missing or ineffective security control: a threat the system was never designed to resist, a business workflow that can be abused as intended, or a trust assumption that does not hold against a real adversary. A secure design can be implemented insecurely, but an insecure design cannot be rescued by a perfect implementation โ€” you cannot correctly implement a control that does not exist.

The practical test: "If every line of code worked exactly as intended, would the system still be exploitable?" If yes, it is a design flaw โ€” not an implementation bug.

A06OWASP 2025 Rank
DesignRoot cause, not a code bug
New '21Introduced (as A04:2021)

โš ๏ธ Top Attack Vectors

  • Workflow step-skipping: POST directly to /confirm, skipping the payment step that a server-side state machine should enforce.
  • Trusting client values: edit the price, role, quantity, or tenant id in the request body.
  • Missing anti-automation: no rate limit enables credential stuffing, OTP brute force, enumeration, scraping.
  • Negative / boundary quantities: a negative amount inverts the arithmetic (CWE-799, CWE-841).
  • Race conditions (TOCTOU): concurrent requests exploit a non-atomic check-then-act (coupon reuse, overdraw).
  • Weak recovery paths: knowledge-based "security questions" whose answers are public.

๐Ÿ”ด Attack Flow

1. Map the workflow's "happy path"
โ†“
2. Ask "what does the server TRUST here?"
โ†“
3. Break the assumption (replay, edit, negate, parallelize)
โ†“
4. Server accepts the illegitimate state
โ†“
5. BREACH: fraud, free goods, or resource exhaustion!

โŒ Vulnerable Design

# Python/Flask โ€” the client tells the server what things cost @app.route('/checkout', methods=['POST']) def checkout(): data = request.get_json() # DESIGN FLAW: price and total come straight from the client total = sum(item['price'] * item['qty'] for item in data['items']) charge_card(data['card_token'], total) return jsonify({'charged': total}) # Attacker POSTs {"items":[{"sku":"LAPTOP","price":1,"qty":1}]}

โœ… Secure Design

# Price is looked up server-side; quantity is bounded; total can't invert @app.route('/checkout', methods=['POST']) def checkout(): data = request.get_json() total = Decimal('0') for item in data['items']: product = Catalog.get_or_404(item['sku']) # trusted catalog qty = int(item['qty']) if qty < 1 or qty > product.max_per_order: abort(400, 'invalid quantity') total += product.price * qty # server computes total if total <= 0: abort(400, 'invalid total') charge_card(data['card_token'], total) return jsonify({'charged': str(total)})

โœ“ Prevention Checklist

  • Threat model each feature (STRIDE) before you build it
  • Write security requirements & abuse cases beside functional stories
  • Model workflows as an authoritative server-side state machine
  • Derive every price, role, limit & tenant server-side โ€” never trust the client
  • Design in rate limits, lockouts & resource caps on sensitive endpoints
  • Use atomic reservation, not check-then-act, for limited resources
  • Reuse vetted secure design patterns & a "paved-road" reference architecture
  • Run abuse-case tests in CI so the missing control can't regress

๐Ÿ” Detection & Tools

Threat Modeling STRIDE Burp Suite OWASP Cornucopia Abuse-case tests Architecture review

How to Test:

  • Drive the workflow off the happy path โ€” skip steps, replay, negate values
  • Fire N concurrent requests at any one-time benefit (TOCTOU)
  • Edit prices, ids, and step order to see what the server actually trusts
  • Watch for impossible outcomes: negative totals, discounts exceeding price
Key Takeaway: Every design flaw reduces to one sentence โ€” "the server trusted something it should have verified." You cannot scan or WAF your way out of a missing control; the only durable fix is to shift left and design the control in.
Myth to drop: "Security can be bolted on later." Architectural controls (rate limiting, trust boundaries, workflow integrity) require re-designing the feature to add afterward โ€” designing them in is far cheaper.