Broken Access Control & IDOR: Reading Other People's Data
The last two lessons involved smuggling something in, a quote, a script tag. This one needs no payload at all. You just ask for something that isn't yours, and a careless app hands it over. Broken access control is consistently the number one risk on the OWASP Top 10, and its most common form, IDOR, is the easiest bug in this whole week to find.
Authentication vs authorization
Two words that sound alike and mean very different things:
- Authentication , who are you? Logging in proves your identity.
- Authorization , what are you allowed to do? Checking that you may access a specific thing.
Broken access control is an authorization failure. The app knows exactly who you are, it just forgets to check whether you're allowed to see what you asked for.
IDOR: the identifier you can change
The classic example. You log in and view your own order:
https://shop.example/order?id=3001
That id=3001 is an Insecure Direct Object Reference, a raw pointer to a database object, sitting right there in the URL. So you try:
https://shop.example/order?id=3002
If the app shows you order 3002, someone else's order, it never checked that the order belongs to you. You've just read a stranger's data by typing a different number. Now imagine a script walking 3000, 3001, 3002, ... and downloading every order in the shop. That's how IDOR turns one changed digit into a full data breach.
IDORs hide everywhere identifiers appear:
/account?id=1005 profile of another user
/invoice/8842.pdf someone else's invoice
/api/messages?user=27 another user's private messages
POST /api/user/role {"id": 5, "role": "admin"} privilege change
Why it's so common
- No tools needed. A browser or
curland a bit of curiosity. - Easy to forget. Developers write the "fetch object by id" code and forget to add "...but only if it's yours" on every single endpoint.
- The UI hides it, the server doesn't. Just because the website never shows you a link to
id=1005doesn't mean the server refuses the request. Hiding is not access control.
Broken access control is bigger than IDOR
IDOR is the poster child, but the family includes anything where the check is missing:
- Visiting
/admindirectly because it's only hidden, not protected. - Changing
role=usertorole=adminin a request and having it accepted. - Reaching an API endpoint the mobile app uses but the website never exposes.
The common thread: the app relies on you not trying, instead of enforcing the rule.
The fix
Every request that touches an object must, on the server, confirm the logged-in user is allowed to access that specific object:
order = db.get_order(id)
if order.owner_id != current_user.id:
return "403 Forbidden" # the check that was missing
Never rely on ids being random, on the UI hiding a link, or on the client behaving. Access control is decided on the server, on every request, or it isn't access control at all.
Try it (terminal)
The lab beside this lesson has an account endpoint keyed by id. Your own id is 1004.
curl "http://10.10.10.20/account?id=1004", your own account, as expected.curl "http://10.10.10.20/account?id=1005", change one digit. Whose data comes back?- That other user's record holds the flag, because the app never checked it wasn't yours.
Why this matters
Broken access control is number one on the OWASP Top 10 for a reason: it's everywhere, it's trivial to exploit, and it leaks exactly the data an app is supposed to protect. No injection, no script, just asking for something and not being told no. Once you start changing ids on every site you're allowed to test, you'll be amazed how often the answer comes back.