The OWASP Top 10 for System Designers
The ten most common ways web applications get breached, framed for design decisions rather than
code review. Knowing these turns “is it secure?” from a shrug into a checklist.
Prerequisites: AuthN vs AuthZ, Sessions/JWT/OAuth
Time to read: ~22 minutes
Why this list
The OWASP Top 10 is the industry-standard list of the most critical web application security risks,
updated every few years from real breach data. 🚨 You don’t need to memorize all ten verbatim, but
knowing the top few — and being able to say how your design defends against them — is a genuine
interview and job skill. Security questions in design interviews almost always map to this list.
This chapter frames each as a design concern. (Ordered by the OWASP 2021 ranking, still current.)
1. Broken Access Control 🚨
#1, and the most common. A user does something they shouldn’t be allowed to.
The classic — IDOR: an authenticated user accesses another user’s data by changing an ID.
# ❌ Logged in, but no ownership check — change the ID, read anyone's order
GET /orders/124 → returns order 124 regardless of who owns it
Design defences:
- 🚨 Authorize every resource access, not just the endpoint. Being authenticated isn’t being
authorized for this object. → AuthN vs AuthZ
- Deny by default — access is forbidden unless explicitly granted.
- Enforce authorization server-side, never trust the client (a hidden field, a disabled button).
- Use unguessable IDs as defence in depth (not the
primary control).
- Consistent, centralized authorization logic rather than ad-hoc checks scattered everywhere.
2. Cryptographic Failures
Sensitive data exposed through weak or missing encryption. (Formerly “Sensitive Data Exposure.”)
Failures: transmitting sensitive data over HTTP, storing passwords with fast/no hashing, weak
ciphers, hardcoded keys, unencrypted PII at rest.
Design defences:
- TLS everywhere, sensitive data hashed (passwords) or encrypted (PII) properly.
- Field-level encryption for the crown jewels; proper key management.
- Don’t store what you don’t need — the safest data is data you never collected.
- → Encryption, AuthN
3. Injection 🚨
Untrusted input interpreted as a command. SQL injection is the archetype.
# ❌ SQL injection: input becomes part of the query
query = f"SELECT * FROM users WHERE name = '{user_input}'"
# user_input = "'; DROP TABLE users; --" → catastrophe
Design defences:
- 🚨 Parameterized queries / prepared statements — always. The input is data, never code. This
single practice eliminates SQL injection.
- Never build queries by string concatenation with user input.
- ORMs help but aren’t automatic immunity (raw queries, some methods bypass protection).
- The same principle for other injection types: NoSQL injection, OS command injection (never pass
input to a shell), LDAP injection.
- Validate and sanitize input as defence in depth — allowlists, not denylists.
4. Insecure Design
🚨 A category added to emphasize that security must be designed in, not bolted on. The others are
implementation bugs; this is architectural — missing rate limiting, no defence against business-logic
abuse, no threat modeling.
Design defences:
- Threat modeling — ask “how would an attacker abuse this?” during design.
- Rate limiting on sensitive operations (login, password reset, expensive queries).
→ Rate Limiting
- Business-logic protections — can someone order -1 items and get a refund? Apply a coupon
infinitely? Race a balance check?
- Defence in depth — multiple layers, so one failure isn’t a breach.
This is the category most relevant to system design interviews — it’s about designing for abuse.
5. Security Misconfiguration
Insecure defaults, unnecessary features, verbose errors, missing hardening.
Failures: default passwords, publicly-accessible admin panels, unnecessary open ports, verbose
error messages leaking internals, permissive CORS, cloud storage buckets left public.
🚨 Misconfigured S3 buckets have exposed medical records, voter data, and credentials repeatedly —
it’s one of the most common real-world data breaches, and it’s pure misconfiguration.
Design defences:
- Secure by default — least privilege, minimal surface, closed unless opened.
- Infrastructure as code so configuration is reviewed and consistent.
→ Infrastructure as Code
- Private by default for storage; explicit, audited exceptions.
- Generic error messages to clients; details in logs.
→ Error Handling
- Automated configuration scanning (cloud security posture tools).
6. Vulnerable and Outdated Components
Using dependencies with known vulnerabilities. Your code may be perfect; a library with a public CVE
is the way in.
🚨 Log4Shell (2021) — a critical flaw in the ubiquitous Log4j library — let attackers run arbitrary
code on millions of servers via a log message. It’s the canonical example: your app is compromised
through a dependency you didn’t write.
Design defences:
- Dependency scanning (Dependabot, Snyk,
npm audit) in CI.
- A Software Bill of Materials (SBOM) — know what you actually depend on, transitively.
- Patch promptly, especially for critical CVEs.
- Minimize dependencies — less code you didn’t write is less attack surface.
7. Identification and Authentication Failures
Weak authentication. (Formerly “Broken Authentication.”)
Failures: allowing weak/breached passwords, no MFA, weak session management, credential stuffing
with no protection, exposed session IDs, no account-lockout.
Design defences:
- MFA — the highest-value control.
- Strong password hashing (bcrypt/Argon2), check against breached-password lists.
- Rate limiting and lockout on login to stop brute force / credential stuffing.
- Secure session management — proper cookie flags, short-lived tokens.
→ Sessions/JWT/OAuth
- Same error for “wrong password” and “no such user” to prevent enumeration.
8. Software and Data Integrity Failures
Trusting code or data without verifying integrity. Includes supply-chain attacks and insecure
deserialization.
🚨 The SolarWinds attack compromised a software update that thousands of organizations installed,
trusting it because it was signed and came from a legitimate vendor — a supply-chain attack.
Design defences:
- Verify integrity of dependencies, updates, and CI artifacts (signatures, checksums).
- Never deserialize untrusted data with unsafe deserializers (
pickle, Java Serializable) — it’s
remote code execution. → Serialization
- Secure the CI/CD pipeline — it’s a high-value target that can inject code into everything.
- Signed commits and artifacts.
9. Security Logging and Monitoring Failures
You were breached and didn’t notice. The average breach goes undetected for months.
Design defences:
- Log security events — failed logins, authorization failures, admin actions, unusual patterns.
- Alert on anomalies — a spike in 403s, logins from new geographies, mass data access.
- 🚨 Don’t log secrets or full PII — logs are a breach target too, and over-logging creates a new
exposure. → Observability
- Retain logs long enough for forensic investigation.
- Tamper-resistant logs an attacker can’t erase to cover tracks.
10. Server-Side Request Forgery (SSRF)
🚨 Increasingly important, especially in the cloud. The server is tricked into making requests to
places it shouldn’t.
# ❌ Fetches a URL the user provides — including internal ones
GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# → the cloud metadata endpoint → the server's IAM credentials!
Why it’s dangerous in the cloud: the metadata endpoint (169.254.169.254) hands out the
instance’s credentials to anything that asks from the instance — so tricking the server into
requesting it leaks cloud credentials. This is how several major breaches happened (Capital One, 2019).
Design defences:
- 🚨 Validate and restrict outbound URLs — block internal IPs (
10.x, 169.254.x, localhost),
allowlist permitted destinations.
- Block the metadata endpoint or use IMDSv2 (which requires a session token, defeating naive
SSRF).
- Network segmentation — the service shouldn’t be able to reach internal resources it doesn’t
need. → Least privilege
- Relevant anywhere you fetch a user-supplied URL: webhooks, image
proxies, URL previews, PDF generators.
Two more worth knowing (not in the top 10 but common)
XSS (Cross-Site Scripting) — injecting scripts that run in other users’ browsers, stealing
sessions or data. Defences: output encoding (escape user content), Content-Security-Policy
headers, HttpOnly cookies (so stolen scripts can’t read the session), and framework auto-escaping.
(XSS is folded into Injection in 2021.)
CSRF (Cross-Site Request Forgery) — tricking a logged-in user’s browser into making an unwanted
request. Defences: SameSite cookies (the modern primary defence), CSRF tokens, and checking the
Origin/Referer header. → Sessions
The design mindset
🚨 The unifying principles behind all ten:
- Never trust input — from users, clients, or other services. Validate, parameterize, encode.
- Deny by default — access, network, and features are closed unless explicitly opened.
- Least privilege — every user, service, and credential gets the minimum.
- Defence in depth — multiple layers, so one failure isn’t a breach.
- Assume breach — design so a compromise has limited blast radius, and you’ll detect it.
🎙️ In an interview, when asked about security, don’t recite the list — apply it: “For this API,
the main risks are broken access control, so I’d authorize every resource server-side; injection, so
parameterized queries throughout; and since we fetch user-supplied URLs, SSRF — I’d allowlist
destinations and block the metadata endpoint. Plus rate limiting on auth and TLS everywhere.”
⚖️ Trade-offs
Security always trades against convenience, latency, and development speed:
| Control |
Protects against |
Cost |
| MFA |
Account takeover |
User friction |
| Rate limiting |
Brute force, abuse |
Legitimate heavy users may hit limits |
| Input validation |
Injection, XSS |
Development effort; false rejections |
| Least privilege |
Blast radius |
More access management |
| Logging/monitoring |
Undetected breach |
Cost, and a new PII exposure if done carelessly |
The overarching point: security is a design concern, not a feature to add later, and the cost of
skipping it is a breach.
In the real world
- Capital One (2019) was an SSRF breach — an attacker tricked a misconfigured WAF into fetching
the cloud metadata endpoint, obtained IAM credentials, and exfiltrated 100 million customer records.
It’s the canonical SSRF case study and why #10 rose in importance.
- Log4Shell (2021) demonstrated #6 (vulnerable components) at planetary scale — a single logging
library’s flaw exposed millions of systems, most of whose owners didn’t even know they were running
Log4j (transitive dependency).
- Countless S3 bucket leaks (#5, misconfiguration) — medical records, voter rolls, credentials —
are the most repeated real-world breach, all from storage left public by default or by accident.
🚨 Interview traps
- “Is it secure?” answered with a shrug. Apply the top risks to the specific design.
- Not authorizing per-resource (IDOR/broken access control). The #1 issue.
- String-concatenated queries. Use parameterized queries.
- Trusting client-side validation as security.
- Fetching user-supplied URLs without SSRF protection, especially in the cloud.
- No rate limiting on auth endpoints.
- Verbose errors leaking internals.
- Treating security as an add-on rather than a design concern.
🎙️ Soundbites
- “The top risk here is broken access control, so I’d authorize every resource access server-side, not
just the endpoint — being logged in isn’t being allowed to touch this specific object.”
- “Parameterized queries everywhere — the input is always data, never code. That eliminates SQL
injection regardless of what the user sends.”
- “Since we fetch user-supplied URLs, SSRF is a real risk — I’d allowlist destinations and block the
cloud metadata endpoint, which is exactly how the Capital One breach happened.”
- “Security is a design concern, not a feature. I’d threat-model this: rate limit the auth and
password-reset flows, deny by default, least privilege on service credentials, and TLS everywhere.”
- “Assume breach — I’d design so a compromise has limited blast radius through least privilege and
segmentation, and make sure we’d actually detect it through security logging.”
🛠️ Try it
1. Exploit and fix each of the top three. Build a deliberately vulnerable endpoint and: exploit an
IDOR by changing an ID, exploit SQL injection with ' OR '1'='1, and exploit SSRF by fetching the
metadata endpoint. Then fix each — ownership check, parameterized query, URL allowlist. Doing the
attacks yourself makes the defences stick far better than reading about them. (Use a deliberately
vulnerable app like OWASP Juice Shop.)
2. Run OWASP Juice Shop. It’s an intentionally vulnerable app for practicing every item on this
list, with a scoreboard. Genuinely the best way to learn these hands-on.
3. Scan your own dependencies. Run npm audit / snyk test / pip-audit on a real project.
You’ll almost certainly find known vulnerabilities you didn’t know you had — that’s #6, live.
4. Check a bucket. Audit a cloud storage bucket’s permissions. Is “block public access” on? Would
a misconfiguration expose it? This is the most common real breach, and checking takes two minutes.
Check yourself
1. Why is broken access control the #1 web security risk?
Because it's both extremely common and directly damaging, and the vulnerable code looks correct. The
classic form — IDOR — is an endpoint that checks the user is authenticated but not that they're
authorized for the *specific* resource requested, so changing an ID in the URL returns someone else's
data. It's common because authentication is usually done centrally and well, but per-resource
authorization is scattered across every endpoint and easy to forget on any one of them — and it passes
casual review because there *is* a logged-in user. It's damaging because a single missing ownership
check can expose every record in a table. The defence is to authorize every resource access
server-side and deny by default, treating authentication and per-object authorization as separate,
both-required steps.
2. How do parameterized queries prevent SQL injection?
By separating the query structure from the data. In a string-concatenated query, user input becomes
part of the SQL text, so input like `'; DROP TABLE users; --` is parsed as SQL commands. A
parameterized query sends the query template (`SELECT * FROM users WHERE name = ?`) and the values
*separately* to the database; the database compiles the template first, then treats the parameters
purely as data to substitute — they can never be interpreted as SQL syntax, no matter what characters
they contain. So the malicious input becomes a literal search for a username containing that odd
string, which simply finds nothing, rather than executing. This single practice eliminates SQL
injection entirely, which is why "always parameterize, never concatenate" is the rule.
3. Why is SSRF especially dangerous in cloud environments?
Because of the cloud metadata endpoint. Cloud platforms expose an internal endpoint (typically
`169.254.169.254`) that hands out the instance's IAM credentials to anything requesting it *from the
instance* — it's how the instance authenticates to cloud services. If an attacker can trick your
server into making a request to a URL they control (an SSRF), they can point it at the metadata
endpoint, and the server dutifully fetches its own cloud credentials and returns them, giving the
attacker the instance's permissions across your entire cloud account. This is exactly how the Capital
One breach (100M records) happened. Defences: validate and allowlist outbound URLs, block internal IP
ranges including the metadata address, use IMDSv2 (which requires a token that naive SSRF can't
obtain), and apply least privilege so the instance's role can't do much even if leaked.
4. What does "Insecure Design" add that the other categories don't cover?
The recognition that security is architectural, not just a matter of implementation bugs. The other
categories are mostly *flaws in how something was built* — a missing check, a concatenated query, a
misconfiguration. Insecure Design is about *what wasn't designed at all*: no rate limiting on
sensitive operations, no protection against business-logic abuse (ordering negative quantities,
applying a coupon infinitely, racing a balance check), no threat modeling, no defence in depth. You
can implement every line perfectly and still be insecure if the design never accounted for how an
attacker would abuse the intended functionality. This is the category most relevant to system design
interviews, because it's about designing *for* abuse — asking "how would someone exploit this
feature?" during design rather than patching bugs afterward.
5. What are the unifying principles behind all ten OWASP risks?
Five design principles. **Never trust input** — from users, clients, or other services; validate,
parameterize, and encode everything (covers injection, XSS, SSRF). **Deny by default** — access,
network reachability, and features are closed unless explicitly opened (covers access control,
misconfiguration). **Least privilege** — every user, service, and credential gets the minimum needed,
so a compromise is contained (covers access control, SSRF blast radius, cryptographic key exposure).
**Defence in depth** — multiple independent layers so one failure isn't a breach (covers everything).
**Assume breach** — design so that when a compromise happens it has limited blast radius, and ensure
you'll actually detect it (covers logging/monitoring, integrity). In an interview, applying these to
the specific system — rather than reciting the list — is what demonstrates real security thinking.
Further reading