VVenstap
Application Security

Broken Access Control: Why It's the Most Common Web Vulnerability

Marcus Chen·

Access control failures consistently rank as the most common category of web vulnerability found in real assessments, and the reason isn't that developers are careless — it's that access control is one of the few security properties that can't be verified by pattern-matching against code. A SQL injection vulnerability looks like a SQL injection vulnerability regardless of what the application does. A broken access control vulnerability only reveals itself once you understand who is supposed to be allowed to do what, which means it's fundamentally tied to business logic that a generic scanner has no way to infer.

What broken access control actually looks like

The category covers a wide range of failure modes, but they share a common shape: the application performs an authorization check somewhere, but not everywhere it needs to, or performs the wrong check. Concretely, this shows up as:

  • Horizontal privilege escalation — a user accessing another user's data at the same privilege level, most often through an Insecure Direct Object Reference where an ID in a URL or API payload isn't checked against session ownership.
  • Vertical privilege escalation — a lower-privileged user reaching functionality reserved for a higher role, often because a role check exists on the UI but not on the underlying API endpoint.
  • Missing function-level access control — an admin action is exposed via an API route that assumes only admins would know the endpoint exists, rather than checking the caller's role server-side.
  • CORS misconfiguration — reflecting arbitrary origins with credentials allowed, letting any website make authenticated requests on a victim's behalf.
  • Forced browsing — reaching unlinked but unprotected pages or endpoints by guessing or enumerating URLs.

Why this is hard to catch with tooling alone

Automated scanners are good at finding things that look wrong in isolation — a missing security header, an outdated library, a reflected parameter. They are structurally bad at finding things that are wrong only in relation to a specific user's session and role. To flag an IDOR, a tool would need to know that the /api/invoices/1043 endpoint should only return data belonging to the authenticated user's organization — that's domain knowledge the scanner doesn't have unless it's been specifically configured with authenticated, role-differentiated scan profiles, and even then, most dynamic scanners test each endpoint independently rather than reasoning about cross-user data relationships.

This is precisely why manual testing and structured multi-role assessments remain essential. A tester who logs in as two different low-privileged users, captures their respective object IDs, and then swaps them across sessions will find IDORs in minutes that a scanner run for hours will miss entirely.

Where it hides in modern architectures

Broken access control has gotten easier to introduce, not harder, as architectures have moved toward APIs and microservices. A few patterns worth specifically hunting for:

  • Client-side-only enforcement. A React or Vue frontend hides an "Admin" button for non-admin users, but the corresponding API endpoint doesn't independently verify the role. The UI check is a convenience, not a control.
  • Inconsistent middleware application. A role-check middleware is applied to most routes in a router file but missed on one or two, often the newest ones added under deadline pressure.
  • Trusting client-supplied role or org data. A JWT or request body includes an orgId or role field that the server reads and trusts instead of deriving from the authenticated session server-side.
  • Object-level checks that stop at existence, not ownership. Code that checks "does this record exist" but not "does this record belong to this user or organization" — a subtle but critical distinction, especially in multi-tenant systems.

Building access control that actually holds

The most durable fix is architectural: centralize authorization logic rather than scattering ad hoc checks through route handlers. A single, well-tested authorization layer — whether that's a guard, a middleware, or a policy engine — that every request passes through is far easier to audit than checks embedded inconsistently across dozens of controllers. Deny by default, and require every new route to explicitly declare its required role rather than inheriting an implicit "authenticated is enough" assumption.

  • Enforce authorization server-side on every state-changing and data-returning endpoint, never relying on UI-level hiding.
  • Derive tenant and ownership scope from the authenticated session, never from client-supplied parameters.
  • Test with matched pairs of low-privileged accounts, swapping object IDs and tokens between sessions.
  • Log and alert on authorization denials — a spike in 403s from one account is a strong signal of active probing.
  • Re-test access control after every schema change that introduces new relationships between users, organizations, and resources.

This is also where a platform's own RBAC model earns its keep during testing, not just in production. Venstap's role hierarchy (Admin, Analyst, Viewer) is deliberately used as a test fixture during assessments — findings get tagged to the specific role boundary they violate, and the audit log captures who accessed what, which turns "we think access control is fine" into something you can actually demonstrate to an auditor. Broken access control isn't going away as a top risk, but it is one of the few categories where disciplined testing methodology, more than any single tool, is the differentiator.

#access-control#idor#rbac

Ready to see Venstap in action?

Get a guided walkthrough of scanning, triage, and reporting on your own assets.