A01 / Broken Access Control — OWASP Top 10 00 / 09
A01
OWASP Top 10 · 2025
Broken Access Control
When authentication says who you are — but nothing checks what you're allowed to see.
// optus — 2022 — what made this breach trivial?
Optus 2022 — read the case before you answer
9.8M records · sequential IDs · unauthenticated API

The attacker accessed 9.8 million customer records through an API endpoint. The Australian government called it "not a sophisticated attack." Four technical details were present — all contributed. Which single factor turned this into an automated attack on 9.8 million records?

🌐
Public-facing API
The endpoint was internet-accessible on an inactive subdomain for 3+ months.
🔓
No authentication
The endpoint required no login — anyone who found it could query it.
🔢
Sequential integer IDs
Customer records used incrementing IDs: 1, 2, 3... up to ~10 million.
⚙️
2018 coding error
A code change weakened access controls; fixed on main domain but not subdomain.
// find the vulnerability

Express.js API endpoint. The user is logged in. One missing check lets them read anyone's data. Click the line.

routes/orders.js
// attack walkthrough
step 1 of 4

Bind every query to the authenticated user.

removed
added

The ID from the URL is user input. The ownership check is the authorization layer — without it, authentication alone means nothing.

🎯 the one habit

In every resource query, bind to the authenticated user’s ID. If the ID comes from the URL, that’s user input — verify ownership before returning data.

Node.js
Python
Java
routes/orders.js
click the dot
why this works
// knowledge check
1 / 3

Two Express.js endpoints return a user's profile. Which one prevents IDOR?

Endpoint A
app.get('/profile/:id', (req, res) => { const profile = await db.find( req.params.id ); res.json(profile); });
Endpoint B
app.get('/profile', (req, res) => { const profile = await db.find( req.user.id ); res.json(profile); });
A — Endpoint A: it uses the URL parameter, which can be validated with a UUID check
B — Endpoint B: it derives the user ID from the authenticated session, not the request
C — Both are equally secure if the JWT is valid
// knowledge check
2 / 3

A user tries to access an order that exists but belongs to someone else. What should the server return?

A — 403 Forbidden — "You don't have access to this resource"
B — 404 Not Found — same response as if the order didn't exist
C — 401 Unauthorized — "Please log in again"
// knowledge check
3 / 3

In 2025, OWASP absorbed a previously separate category into A01 Broken Access Control. Which one?

A — Cross-Site Scripting (XSS)
B — Security Misconfiguration
C — Server-Side Request Forgery (SSRF)
// self-check complete

// the mental model

carry this into your next code review

Authentication proves who is asking. Authorization proves they're allowed to access this specific resource. They are two different checks. Both are required.

A01 · Broken Access Control
Scope in 2025
IDOR · privilege escalation · CORS · path traversal · SSRF
A01 has the widest scope in the 2025 Top 10 — 40 mapped CWEs, 1.8 million occurrences. IDOR (Insecure Direct Object Reference) is when your API exposes resource IDs without verifying the requester owns them — exactly what happened at Optus. SSRF (Server-Side Request Forgery) was absorbed from A10:2021 because it's fundamentally an access control failure: the server makes a request to a resource the user shouldn't reach. Privilege escalation, JWT tampering, forced browsing, and CORS misconfig all live here too. SSRF and CORS defenses are shared between application code and network/gateway — DevOps owns part of this category.
First fix
Bind every query to req.user.id — enforce ownership at the data layer
Add AND user_id = ? to every resource query. Use deny-by-default: if the ownership check fails, return 404 — not 403 (which leaks that the resource exists). Implement access control once and reuse it. Never rely on the client to enforce permissions.
Defence in depth
UUIDs · rate limiting · functional access control tests
Replace sequential integer IDs with UUIDs to prevent enumeration. Rate limit API endpoints. Write automated tests that specifically check cross-user access — e.g., user A tries to read user B's resource and expects 404. Log access control failures and alert on spikes. Invalidate session tokens on logout.