APIs & Auth
Broken Access Control: The Complete Practical Guide
What broken access control is, how to find and exploit it (IDOR/BOLA, privilege escalation, mass assignment), and how to prevent it. With free labs.
Published

Broken access control is the gap between knowing who a user is and enforcing what they are allowed to do. The app authenticates you fine, then forgets to check, on each request, whether you may actually see or change the thing you asked for. So a logged-in user reads another customer's record, hits an admin-only route, or flips their own role to administrator. It sits at the top of the OWASP Top 10 because it is everywhere, trivial to exploit once you spot it, and almost invisible to scanners that do not know your business rules. The core move is simple: authenticate as a low-privilege user, then try to do things that user should not be able to do. Want to feel it rather than read about it? Sign up free and try it on a live target.
What is broken access control?
Every app has two separate jobs. First it works out who you are (authentication, the login). Then, on every request, it must decide whether you may do what you are asking (authorization). Broken access control is what happens when the app does the first and skips or botches the second.
The one-sentence model: the app checks who you are, but forgets to check whether you may. Once that check is missing on a route, a parameter, or a step in a flow, you step outside your lane, and the server hands over data or actions that were never yours to touch.
Authentication vs authorization
These get conflated, and the confusion is where the bugs live. A perfect login proves identity. It says nothing about permission. If the server validates your session and then returns whatever record id you pass without checking ownership, the login did its job and authorization still failed.
The rule to hold onto: authorization is not a one-time event at login, it is a decision the server must make on every single request, for the specific object and action involved. Anywhere that decision is made once, made on the client, or skipped, you have a finding.
The two axes: horizontal and vertical
Access control failures run along two axes, and you test both:
- Horizontal is reaching sideways to another user at your own level, like reading a peer's invoice by changing an id.
- Vertical is climbing up to higher privileges, like a standard user performing an admin action.
A horizontal gap often becomes a vertical one (reach an admin's record sideways, then use it to act as admin). Vertical vs horizontal maps which axis each endpoint exposes.
IDOR and BOLA
The most common access control bug is a direct object reference you can tamper with. The app resolves an object by the id you supply (/orders/1003) and returns it without checking you own it, so you walk the ids to other people's records. In APIs this exact bug is called BOLA. IDOR and BOLA covers numeric, UUID, and encoded references, where they hide, and write-IDOR (editing or deleting someone else's object).
Privilege escalation
Beyond reading other records, the goal is often to gain rights you were not given. That can be as blunt as adding a role field to a profile update, or as subtle as abusing an "act as" feature or an export endpoint that runs with higher privileges. Privilege escalation covers the techniques, and parameter-based access control covers the root cause behind many of them: apps that trust a role or flag the client supplies.
Platform and framework gaps
Sometimes the authorization logic is fine but the gate in front of it can be slipped. A proxy that allows only /public can be told to route elsewhere with a header override, a rule that only blocks GET can be beaten with another verb, and a gate that matches URLs differently from the app can be walked around with normalization tricks. See platform misconfiguration and URL-matching discrepancies.
Logic and flow gaps
Access control also breaks in the shape of a workflow. A multistep process that only checks your role on step one lets you jump to a later step, and a gate that trusts the Referer header or a client-supplied redirect target is trusting something you control. See multistep process flaws and Referer and Location-based bypass.
Mass assignment
When an app binds a whole request body straight onto a model, you can set fields the form never exposed, like your own role or isAdmin, or an ownership key that seizes another record. Mass assignment covers how auto-binding leaks these fields and how to discover which ones are bindable.
Chaining to account takeover
Individually, these gaps look small. Chained, they take over accounts: an IDOR into an email-change, a role flip followed by a password reset, a mass-assignment combined with a horizontal gap. Account-takeover chains shows how small access failures compound into full control of another account.
How to find broken access control
The method is methodical, not clever:
- Map every role the app has, including anonymous.
- Record every endpoint each role can reach and every action it can take.
- Replay each privileged request as a lower-privilege user, then as an unauthenticated one. Does it still work?
- Tamper every reference (ids, owner fields, encoded values) to a peer's value.
The cheat sheet has the full triage checklist and the payloads for each class.
A worked example
Say a billing page loads your invoice at /account/invoices/4012. You change the number to 4011 and it returns a different customer's invoice, complete with their name and address. That is a read IDOR, horizontal.
You keep walking and find /account/invoices/1 returns an invoice belonging to the first account ever created, an internal admin. Its page links to an action, /admin/invoices/1/refund, that your own session is not supposed to reach. You request it directly, forcing the URL the UI never showed you, and the refund goes through. A numeric id you could edit turned into an admin-only action you could perform. That is the loop: tamper a reference, reach something that is not yours, then push from data access toward a privileged action.
Prevention
- Deny by default. Grant access only through explicit allow rules, evaluated server-side on every single request, never once at login.
- Authorize the object, not just the route. Before returning or mutating a record, confirm the current user owns or is permitted that specific object.
- Never trust client-supplied authorization data. Roles, flags, ids-of-self, headers (
X-Original-URL,X-Forwarded-For,Referer), and method overrides are all attacker-controlled. - Bind write fields with an allow-list. Never auto-bind the whole request body, so
role,isAdmin, and ownership keys can never be set by the client. - Normalize URLs identically at the gateway and the app, enforce role checks at every step of a multistep flow, and alert on authorization failures.
Unpredictable references (UUIDs instead of sequential ids) help, but treat them as defense in depth: the authorization check is still mandatory for every reference.
Where to go next
Work through the cluster in order, or jump to what you need. Start with IDOR and BOLA, then vertical vs horizontal and privilege escalation. When you want every payload and the triage checklist in one place, the cheat sheet has them. And when you are ready to break something real, sign up free and start hacking.