Broken access control overtook injection as the number one web vulnerability in the OWASP Top 10, and the data supports the ranking. Over 94% of applications tested showed some form of access control failure. Unlike injection attacks, which can be mitigated with parameterised queries, access control is a logic problem — there's no single technical fix, and every endpoint is a potential failure point.
What Broken Access Control Looks Like
Access control failures occur when a user can act outside their intended permissions. The manifestations are varied:
- Insecure Direct Object References (IDOR): Changing a parameter —
/api/orders/1234to/api/orders/1235— returns another user's data because the server checks authentication but not authorisation for that specific resource. - Privilege escalation: A standard user discovers an admin endpoint —
/api/admin/users— that's accessible because the route exists but doesn't verify the caller's role. - Path traversal: Manipulating file paths —
../../etc/passwd— to access files outside the intended directory when the application serves static content based on user input. - Metadata manipulation: Modifying a JWT, cookie, or hidden form field to change role, user ID, or permission claims that the server trusts without re-verification.
- Force browsing: Accessing pages that aren't linked in the UI but exist on the server —
/debug,/admin,/internal— because access control was implemented in the frontend only.
Why IDOR Is So Common
IDOR deserves special attention because it appears in virtually every application that uses numeric or predictable identifiers. The pattern is universal: an API endpoint accepts a resource ID and returns the corresponding data. The developer implements authentication — the user must be logged in — but forgets authorisation — verifying this specific user owns this specific resource.
// Vulnerable endpoint
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.invoices.findById(req.params.id)
res.json(invoice) // No check: does req.user own this invoice?
})
// Fixed endpoint
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.invoices.findOne({
_id: req.params.id,
userId: req.user.id // Scoped to authenticated user
})
if (!invoice) return res.status(404).json({ error: 'Not found' })
res.json(invoice)
})
The fix is conceptually simple — scope every query to the authenticated user. But applying this consistently across hundreds of endpoints, including edge cases like shared resources, delegated access, and hierarchical permissions, is where implementations fail.
The Frontend Is Not a Security Boundary
A recurring pattern in access control failures is relying on the frontend to enforce permissions. The UI hides the "Delete" button for non-admin users, but the DELETE /api/resource/:id endpoint doesn't check the caller's role. The admin panel is a separate React route that non-admin users never see — but the API endpoints it calls are accessible to anyone with a valid session token.
Attackers don't use your frontend. They use curl, Postman, or custom scripts. Every API endpoint must independently verify authorisation, regardless of whether the frontend displays the corresponding UI element.
Horizontal vs Vertical Escalation
Access control failures fall into two categories:
- Horizontal escalation: A user accesses another user's data at the same privilege level. User A reads User B's invoices, messages, or profile. This is the IDOR pattern.
- Vertical escalation: A user performs actions above their privilege level. A regular user accesses admin functions, a free-tier user accesses premium features, or a read-only user modifies data.
Both are equally dangerous. Horizontal escalation enables mass data theft across all users. Vertical escalation enables complete system compromise through administrative access.
Why Access Control Is Hard
Unlike SQL injection (use parameterised queries) or XSS (use output encoding), there's no universal pattern that prevents all access control failures. Each endpoint has unique authorisation requirements:
- This resource is owned by a single user
- This resource is shared among a team
- This resource is readable by anyone but writable by the owner
- This action requires the "editor" role within a specific organisation
- This resource is accessible to the owner's manager but not peers
The authorisation model is a reflection of business logic, and business logic varies across every endpoint. There's no library or framework that can auto-generate correct access control rules — they must be explicitly defined and consistently enforced.
ShieldReport identifies the external indicators that correlate with access control weaknesses — exposed API patterns, missing security headers that enable attack chains, and configuration signals that suggest authorisation hasn't been hardened — giving you visibility into your application's risk posture.