Broken Access Control: OWASP's #1 Web Application Vulnerability
When the OWASP Foundation published its 2021 Top 10, broken access control moved from fifth place to first. Not because it was newly discovered — authorization flaws have existed as long as multi-user applications — but because the data showed it had become the single most prevalent vulnerability class in modern web applications. Testing data revealed that 94% of applications had some form of broken access control.
That number should concern anyone building or operating a web application. Access control is the mechanism that determines what a user can see, do, modify, and delete. When it breaks, everything behind it is exposed.
What Access Control Actually Means
Access control is the enforcement layer between authentication (proving who you are) and authorization (defining what you're allowed to do). Authentication might work perfectly — your login system is solid, passwords are hashed, sessions are managed correctly — but if the application doesn't enforce authorization on every request to every resource, authenticated users can still reach things they shouldn't.
There are three fundamental dimensions of access control:
Vertical access control restricts functionality based on user roles. Regular users shouldn't access admin panels. Support agents shouldn't modify billing configurations. Read-only API keys shouldn't perform write operations.
Horizontal access control restricts resources based on ownership. User A shouldn't read User B's messages. Customer 1234 shouldn't download Customer 5678's invoices. One tenant in a multi-tenant system shouldn't see another tenant's data.
Context-dependent access control restricts actions based on application state or workflow. You shouldn't be able to confirm an order after cancelling it. You shouldn't be able to modify a form submission after it's been approved. You shouldn't be able to use a single-use token twice.
When any of these dimensions fail, you have broken access control.
How Broken Access Control Manifests
Horizontal Privilege Escalation
This is the most common pattern we encounter in security assessments, and it often takes a deceptively simple form.
Consider an application that displays user profile information at a URL like /api/users/4821/profile. The application loads the profile for user ID 4821. A logged-in user with ID 4821 sees their own data. But what happens when that user changes the request to /api/users/4822/profile?
If the server returns user 4822's profile without verifying that the requesting user is authorized to view it, that's an Insecure Direct Object Reference — one of the most prevalent forms of broken access control. The application authenticates the request (valid session token) but doesn't authorize it (does this user have permission to access this specific resource?).
This pattern shows up everywhere: order histories, transaction records, uploaded documents, support tickets, private messages. Any endpoint that takes a user-controllable identifier and returns data associated with that identifier is a candidate for IDOR testing.
During our assessments of over 400 targets, IDOR vulnerabilities appear in roughly one out of every three engagements. They're common because developers often assume that if a user is authenticated, they should be able to access the endpoint — forgetting that the endpoint serves data for all users, not just the one making the request.
Vertical Privilege Escalation
Vertical escalation occurs when a lower-privileged user accesses functionality reserved for higher-privileged roles. The classic example is a regular user accessing administrative functionality.
This often happens when applications rely on client-side controls to hide admin features. The navigation menu doesn't show the "Admin" link for regular users, but the underlying endpoint — /api/admin/users or /internal/dashboard — exists and responds to any authenticated request. The UI hides the button, but the server doesn't enforce the restriction.
We've seen this pattern take several forms:
Missing function-level access checks. The endpoint exists, works, and returns data. It simply never verifies the caller's role. The developer built the feature for admins, tested it as an admin, and never considered what happens when a non-admin makes the same request.
Role check on the wrong layer. The front-end checks the user's role before rendering the UI component, but the API endpoint behind it has no role verification. An attacker who skips the UI entirely — sending requests directly to the API — bypasses the check completely.
Inconsistent enforcement across HTTP methods. The GET request to /api/settings properly checks for admin privileges, but the PUT or PATCH request to the same endpoint doesn't. The developer protected the read path but forgot the write path.
Metadata Manipulation
Some access control flaws involve manipulating metadata that the application trusts. This includes:
JWT claim tampering. If the application reads the user's role from an unvalidated JWT claim — or worse, from a cookie or hidden form field — an attacker can modify the value to escalate privileges. We've encountered applications where changing "role": "user" to "role": "admin" in the JWT payload granted full administrative access because the server never validated the token's signature.
Parameter-based access control. Applications that pass access control decisions through request parameters — ?admin=true, ?role=manager, ?access_level=3 — are trivially exploitable. Any value the client can modify should never be trusted for authorization decisions.
Forced browsing. Accessing URLs or API endpoints that aren't linked from the UI but exist on the server. Admin panels, debug endpoints, internal tools, API documentation pages — if they're deployed and reachable, someone will find them.
Missing Access Control on Static Resources
Applications that generate user-specific documents — invoices, reports, contracts — sometimes store them with predictable filenames in publicly accessible storage. The application's UI only shows each user their own documents, but the underlying files at /uploads/invoice-4821.pdf can be accessed by anyone who guesses or enumerates the filename.
This extends to cloud storage configurations. Misconfigured storage buckets that allow public listing or access by URL pattern turn private documents into public ones.
Why Scanners Can't Find These
Automated vulnerability scanners are effective at detecting technical vulnerabilities with clear signatures — missing security headers, outdated software versions, known CVEs, SQL injection in input fields. But broken access control is fundamentally a logic problem.
A scanner doesn't know your authorization model. It doesn't know that user A shouldn't see user B's data. It can't distinguish between an endpoint that intentionally returns public data and one that's leaking private data due to a missing authorization check. It doesn't understand that the /admin panel should only be accessible to administrators.
Detecting access control failures requires:
- Understanding the application's intended authorization model
- Authenticating as multiple users with different privilege levels
- Systematically testing whether each role can access resources and functions belonging to other roles
- Understanding business context — which data is sensitive, which operations are privileged
This is inherently manual work. It's one of the primary reasons that manual security testing catches critical vulnerabilities that years of automated scanning missed. Across our 1,400+ findings, broken access control consistently ranks among the most severe — and the most frequently missed by prior automated assessments.
Real-World Impact
The consequences of broken access control depend on what's exposed, but they're rarely minor:
Data exposure. Unauthorized access to user records, financial data, health information, or personal documents. In regulated industries, this triggers breach notification requirements and regulatory penalties.
Account takeover. If access control flaws expose password reset tokens, session identifiers, or authentication credentials, attackers can take over accounts entirely.
Data modification or deletion. Write-path access control failures let attackers modify records — changing prices, altering transactions, deleting data, or injecting malicious content.
Full administrative compromise. Vertical escalation to admin privileges often means access to all user data, system configuration, and the ability to create backdoor accounts for persistent access.
Prevention: Building Access Control That Works
Deny by Default
The most important architectural decision is the default behavior. If no explicit rule grants access, the request should be denied. This is the opposite of how many applications are built — where everything is accessible unless specifically restricted.
Implement a centralized authorization middleware that runs before every request handler. If the request doesn't match an explicit allow rule, it gets rejected. This ensures that new endpoints are secure by default — a developer who forgets to add authorization rules gets a denied request, not an open endpoint.
Role-Based Access Control (RBAC) with Resource-Level Checks
RBAC assigns permissions to roles, and roles to users. But role-based checks alone are insufficient. Knowing that a user has the "customer" role tells you they can access customer features — but it doesn't tell you which customer's data they can access.
Every data-access operation needs two checks:
- Role check: Does this user's role permit this type of operation?
- Resource check: Does this user own (or have explicit access to) this specific resource?
Both checks must happen server-side, on every request, regardless of how the user reached the endpoint.
Server-Side Enforcement
Never trust client-side access control. The UI can hide buttons, disable fields, and omit menu items for user experience purposes, but every restriction must be independently enforced on the server. Assume that every API endpoint will be called directly, without the UI, by someone who has read your JavaScript and mapped every route.
Avoid Exposing Internal Identifiers
Where possible, use indirect references instead of exposing database IDs in URLs and request parameters. Map user-facing identifiers to internal records through a server-side lookup that incorporates the current user's permissions. If you must use direct identifiers, ensure every access is checked against the requesting user's authorization context.
Audit Logging and Rate Limiting
Log every access control failure. A user who triggers dozens of "access denied" responses against sequential resource IDs is probing for IDOR vulnerabilities. Aggregate these logs, alert on patterns, and investigate anomalies.
Rate limiting on sensitive endpoints adds a practical barrier to enumeration attacks, even when the underlying access control is properly enforced.
Automated Testing for Access Control
While scanners can't detect access control logic flaws, you can build automated tests that do. Integration tests that authenticate as User A and attempt to access User B's resources — expecting a 403 — catch regressions when authorization logic is accidentally modified or removed during refactoring.
These tests should cover every endpoint, every HTTP method, and every role combination. They become part of your CI/CD pipeline and catch access control regressions before they reach production.
Testing Methodology
When we assess an application for access control vulnerabilities, the process is systematic:
- Map all endpoints and functions — identify every API route, page, and action available in the application
- Identify roles and permission levels — understand the intended authorization model
- Create test accounts at each privilege level — regular user, moderator, admin, API consumer, etc.
- Cross-test every endpoint — authenticate as each role and attempt to access every endpoint intended for other roles
- Test resource-level access — authenticate as one user and attempt to access specific resources belonging to another user
- Test state-dependent controls — attempt to perform actions outside their intended workflow context
- Examine metadata and tokens — check whether authorization decisions rely on client-controllable values
This systematic approach catches flaws that ad-hoc testing misses. It's labor-intensive, which is precisely why it's valuable — attackers are thorough, and your testing should be too.
Conclusion
Broken access control is OWASP's top vulnerability for a reason. It's pervasive, it's impactful, and it's invisible to automated scanning. Every application that manages users, roles, or multi-tenant data is a candidate for these flaws. The fix isn't a single patch — it's an architectural commitment to deny-by-default policies, server-side enforcement, and resource-level authorization on every request.
Need your application tested for broken access control? Get in touch.