An access control vulnerability happens when an application lets a user do something the app never meant to allow. Broken access control is the name for this whole family of bugs, and it sits at the top of the OWASP Top 10 because it shows up everywhere and the damage is direct. If you have ever wondered what is access control vulnerability in plain terms, it is this: the server forgot to check whether you are allowed before it did what you asked.
What is broken access control vulnerability, in one sentence
Access control is the set of rules that decide who can see and change what. Authentication answers “who are you.” Access control answers “are you allowed to do this specific thing right now.” When that second check is missing, weak, or only enforced in the browser, you get a broken access control flaw. The user proves who they are, then reaches data or actions that should be off limits to them.
Here is the part that surprises beginners. The login can be perfect. Passwords can be strong, sessions can be secure, and the bug still exists. Access control is a separate gate, and it has to be checked on every request that touches protected data.
Why broken access control is the number one OWASP risk
Three reasons put it first.
- It is common. Almost every app has many endpoints, and each one needs its own check. Miss one and you have a hole.
- It is easy to trigger. Many of these bugs need nothing more than a changed number in a URL or a flipped value in a request body. No special tools.
- The impact is plain. Read another person’s records, delete data you do not own, or reach an admin function. There is no fancy exploit chain in between.
If the server does not ask “is this user allowed to do this” on every request, the answer is no by accident.
Three simple examples
These use an invented app, Acme Notes, where people store private notes. None of this targets a real system.
1. Changing an id in a URL. You open your own note and the address looks like this.
GET /notes/1024 Cookie: session=your_own_valid_session
You change the number to a note that is not yours.
GET /notes/1025
If Acme Notes returns note 1025 without checking that it belongs to you, that is a broken object level access control bug. People often call this an insecure direct object reference, or IDOR. The id is a direct pointer to an object, and nothing stops you from pointing at someone else’s.
2. Forcing your way to an admin page. The app hides the admin link from normal users, so the menu never shows it. But the route still exists.
GET /admin/users
You type the path by hand. If the server renders the admin user list because you happen to be logged in as anyone, the protection was only in the menu, not in the code that serves the page.
3. Editing a request to act as another user. When you update your profile, the browser sends a body like this.
POST /profile/update
{ "user_id": 1024, "email": "you@example.com" }
You change user_id to someone else.
POST /profile/update
{ "user_id": 1025, "email": "attacker@example.com" }
If the server trusts the user_id in the body instead of the user tied to your session, you just changed a stranger’s email. The fix is to ignore that field entirely and use the identity from the session.
Horizontal and vertical access control
Two words help you reason about these bugs.
Horizontal access control
This is about users at the same level. You and another customer both have normal accounts. Horizontal access control keeps you inside your own data. The note id example above is a horizontal failure: one regular user reached another regular user’s note. The roles match, but the owner does not.
Vertical access control
This is about levels of power. A normal user should not reach actions reserved for an admin or a moderator. The admin page example is a vertical failure: a low privilege user reached a high privilege function. You climbed a level you were never granted.
Many real bugs are one or the other. Some are both at once, like a regular user who can both read other people’s data and trigger admin only actions through the same weak endpoint.
How to spot broken access control
You find these bugs by asking, for every request, “what is being trusted here, and who set it.” Walk through the app with two accounts and try the obvious moves.
- Change identifiers. Swap ids in URLs, query strings, and request bodies. Try ids that belong to a second account you control. Watch for data that is not yours.
- Visit hidden routes directly. List the admin and settings paths you can find, then request them as a low privilege user. A redirect or a 403 is good. A real response is a finding.
- Replay actions across roles. Capture a request that only an admin should make, then send it from a normal session. If it works, vertical control is broken.
- Look for client side gates. If a button is hidden but the underlying API still answers, the check lives in the wrong place.
- Test every method. An endpoint might block
GETbut allowDELETEorPUT. Try them.
The mindset that finds the most is understanding what the app assumes. The note example only works because the app assumes you will never edit the id. Question that assumption and the bug appears. You can read more grouped writing on this topic in the access control category.
How to prevent broken access control
The core rule is short. Check authorization on the server, for every request, against the identity in the session, not against anything the client sent.
- Deny by default. Start with everything closed. Open access on purpose, per route, never by forgetting to block it.
- Decide on the server. The browser can hide a button to keep the screen clean, but it can never be the gate. The real check lives in code the user cannot touch.
- Tie ownership to the session. When loading note 1025, confirm the note’s owner matches the logged in user. Do not trust a
user_idfrom the request body. - Centralize the rules. One shared function that answers “can this user do this action on this object” is easier to get right than checks copied into every handler.
- Avoid guessable ids where you can. Random identifiers are not a real defense on their own, but they raise the cost of blind guessing while your checks do the work.
- Test it like a feature. Write checks that try one user’s id from another user’s session and confirm they fail. Run them on every change so the gap cannot return quietly.
Putting it together
Broken access control is the number one OWASP risk because the bug is simple, common, and high impact, and each new endpoint is one more place to forget the check. Spotting it means thinking about what the app trusts. Preventing it means checking authorization on the server for every request, using the identity you control rather than the data the user sent.
This is exactly the kind of bug an autonomous researcher that tests assumptions is built to find, because it lives in the gap between how an app is meant to work and what it actually allows. Learn more on the about page.
