Broken Access Control and IDOR
CAP, ACID vs BASE, latency numbers, back-of-envelope estimation, single points of failure — the vocabulary every system designer thinks in.
Core Philosophy: Authentication proves who you are; authorization decides what you’re allowed to do. Broken access control is the failure of that second check — the application correctly knows who you are, then fails to verify that this user may access this resource or this action. It is consistently among the most widespread and most damaging vulnerability categories in the entire industry, and — good news for you — it’s often found without any special tools at all.
Part 1: The Problem
Recall the airport analogy from 1.4: authentication is showing your passport; authorization is your boarding pass deciding which flight and seat you may board. Broken access control is being correctly identified at security, then walking onto any plane you like because nobody checks the boarding pass.
In application terms: you log in successfully (authentication works), but then the app fails to properly enforce what you specifically are allowed to see and do. You access another user’s data. You reach an admin function. You perform an action meant for someone else.
It is enormously common — it has ranked at or near the top of the OWASP Top 10 — for a simple reason: every single resource and every single action in an app needs a correct permission check, and missing even one creates the hole. It’s also a favourite of bug bounty hunters because the bugs are high-impact and frequently require nothing more than changing a value in a request.
Part 2: The Concept — IDOR (Insecure Direct Object Reference)
The most classic broken-access-control bug is the IDOR. The name sounds technical; the idea is simple.
Applications refer to things — invoices, profiles, messages, files — by identifiers, often in the URL or in request parameters:
/invoice?id=4012
/user/3375/profile
/api/messages/88291
/download?file=report_204.pdf
An IDOR exists when the app uses that identifier to fetch the object but never checks that the requesting user is allowed to access that object. So the attacker simply changes the identifier:
You are logged in. You view YOUR invoice:
/invoice?id=4012 ✓ your invoice
You change the number:
/invoice?id=4011 ← someone else's invoice
/invoice?id=4013 ← another person's invoice
If the app shows them to you, that is an IDOR.
It fetched the object by ID and never asked
"is this user allowed to see object 4011?"
That’s it. The “attack” can be as trivial as editing a number in a URL. Yet the impact can be enormous — every user’s invoices, every user’s personal data, every user’s private messages, just by walking the IDs.
Part 3: The Forms of Broken Access Control
IDOR is the famous example, but broken access control is a broader family. The main forms:
Horizontal access control failure. Accessing the data of another user at your same privilege level — one regular user reading another regular user’s data. IDOR is usually this.
Vertical access control failure (privilege escalation). Accessing functionality of a higher privilege level — a normal user reaching admin-only functions.
HORIZONTAL VERTICAL
user A ──► user B's data normal user ──► admin functions
(same privilege level) (higher privilege level)
Forced browsing. Directly requesting a URL you were never shown but which isn’t properly protected — e.g. navigating straight to /admin/dashboard. The app “hid” the link but didn’t actually protect the page. (Hiding is not access control.)
Missing function-level access control. The app checks permissions on the page but not on the underlying actions/APIs. The admin page is protected, but the admin API endpoint it calls is not — so an attacker calls the API directly.
Parameter / metadata tampering. Changing a value that the app trusts to make access decisions — e.g. a request containing role=user that the attacker changes to role=admin, or a hidden field, or a cookie value, that the server trusts instead of verifying server-side.
The common thread in every form: the app failed to properly verify, on the server, that this specific user is authorized for this specific resource or action.
Part 4: Why It Happens — The Root Causes
Understanding why broken access control is so common makes you better at finding it and, later, fixing it.
- Checks are easy to forget. Every endpoint, every action, every object needs its own check. Developers add a new feature and simply forget the permission check on it. One forgotten check = one hole.
- Trusting the client. The app “hides” the admin button from normal users and assumes that’s enough — forgetting that the attacker controls the browser entirely (sections 1.4 and 2.2) and can request anything directly. Hiding something is not protecting it.
- Trusting identifiers and parameters. The app assumes that because it gave you a URL with
id=4012, you’ll only ever use4012. Attackers don’t honour assumptions. - Insecure defaults. New objects or endpoints default to “accessible” rather than “denied.”
- Inconsistent enforcement. Access control logic is scattered across the codebase instead of centralized, so coverage is patchy.
This is also why insecure design (an OWASP category) and broken access control are linked — robust access control has to be designed in and enforced consistently, not sprinkled around ad hoc. You’ll see the design-level fix in Phase 4.3.
Part 5: Finding Broken Access Control
This category rewards methodical testing, and you already have the tools (Burp, from 2.2). The approach:
1. MAP ROLES Identify the privilege levels: anonymous,
regular user, admin, etc.
2. USE TWO Create two accounts (e.g. User A and User B).
ACCOUNTS This is the key technique for horizontal
access-control testing.
3. CATALOGUE As User A, browse the whole app. Note every
identifier, endpoint, and action.
4. CROSS-ACCESS As User B, try to access User A's objects
(swap IDs). Try to reach admin functions
as a normal user. Try forced browsing to
unlinked pages.
5. TAMPER Modify parameters, hidden fields, cookies,
and roles in requests — see what the server
trusts that it shouldn't.
6. TEST THE API Check whether underlying API endpoints enforce
the same controls as the pages.
The two-account technique is the heart of it: log in as User B, capture a request, then swap in identifiers belonging to User A. If User A’s data comes back, you’ve found an IDOR. Burp’s Repeater makes this swap-and-resend loop fast.
⚖️ Ethics — critically important here. Broken access control means you can reach other people’s real data. On any target with real users — especially a bug bounty target — stop the instant you’ve proven access is possible. Confirm with the minimum — ideally your own second test account, or one record, enough to demonstrate the flaw. Do not enumerate through real users’ data “to show impact.” Reading other people’s personal data is a serious crime and a serious harm, in-scope program or not. Prove it; never pillage it. This is page 1.0 at its most important.
Part 6: A Glance at the Fix
The fix is conceptually simple and operationally demanding (built fully in Phase 4.2 / 4.3):
- Enforce every access check on the server, never the client. The browser is attacker-controlled; permission decisions cannot live there.
- Check authorization for every request — every resource access, every action, every API endpoint. Not just the page; the action behind it.
- Verify ownership / permission, not just identity. Don’t ask only “is this user logged in?” Ask “is this user allowed this specific object/action?”
- Deny by default. Access starts denied and is explicitly granted — so a forgotten check fails safe (locked) rather than open.
- Centralize access-control logic. One consistent, well-tested mechanism, not scattered ad-hoc checks, so coverage is complete.
- Don’t rely on unpredictable IDs alone. Hard-to-guess identifiers raise the bar slightly but are not access control — the permission check is still mandatory. (This is “security through obscurity,” and obscurity is never a substitute for a real control.)
VULNERABLE: fetch invoice by the ID in the request,
return it. ← no permission check
SAFE: fetch invoice by ID, THEN verify the
logged-in user owns/may access it,
THEN return it — else DENY.
📓 Key Terms
| Term | Plain meaning |
|---|---|
| Access control / authorization | Enforcing what an authenticated user may access/do. |
| Broken access control | Failure to properly enforce those permissions. |
| IDOR | Insecure Direct Object Reference — accessing objects by changing an identifier, with no ownership check. |
| Horizontal access failure | Accessing another user’s data at the same privilege level. |
| Vertical access failure | Accessing higher-privilege (e.g. admin) functionality. |
| Privilege escalation | Gaining permissions above your own. |
| Forced browsing | Directly requesting unlinked-but-unprotected URLs. |
| Parameter tampering | Changing a trusted value (role, ID, hidden field) to gain access. |
| Deny by default | Access is denied unless explicitly granted — so omissions fail safe. |
| Security through obscurity | Relying on secrecy/hard-to-guess values instead of a real control — not sufficient. |
🧪 Hands-On Lab
Only on your own lab or deliberately vulnerable practice apps. Accessing other people’s real data on any unauthorized system is a serious crime — and a serious harm.
Task 1 — Find your first IDOR. On a deliberately vulnerable app, find a feature using an identifier in the URL or a parameter (an invoice, a profile, a document). Change the identifier. If you see another “user’s” data, you’ve found an IDOR. Note how little it took.
Task 2 — Practice the two-account technique. Create two accounts in the practice app. As User B, capture a request for User B’s own data in Burp. In Repeater, swap in User A’s identifier. Does User A’s data return? This is the core professional method for access-control testing.
Task 3 — Vertical escalation. As a normal user in the practice app, try to reach admin functionality — guess admin URLs (forced browsing), or call admin API endpoints directly. See whether “normal user” is actually enforced or merely assumed.
Task 4 — Parameter tampering. Find a request containing a role, a privilege flag, a price, or a hidden field. Modify it in Burp (e.g. role=user → role=admin). See whether the server trusts your modified value instead of verifying server-side.
Task 5 — Page vs API. Find a protected admin page. Identify the API endpoint it uses. Call that endpoint directly as a normal user. This tests for missing function-level access control — a very common real-world gap.
Task 6 — Write it up properly. For one IDOR you found, write a short, professional finding: what the flaw is, the exact reproduction steps, the impact, and the fix from Part 6. This is real bug-report practice (developed fully in Phase 5A.4).
⚠️ Common Mistakes
- Enumerating real users’ data to “prove impact.” The single most serious mistake in this whole category. Prove access is possible with the minimum — your own second account, one record — then STOP. Going further is a crime and a real harm to real people.
- Thinking “hidden” means “protected.” A removed link, a hidden button, an undisplayed page — none is access control. The attacker controls the browser and requests whatever they want.
- Testing only with one account. Horizontal access-control bugs are invisible with a single account. The two-account technique is essential.
- Checking pages but not APIs/actions. A protected page calling an unprotected API is a classic gap. Always test the underlying endpoints.
- Relying on unpredictable IDs as the fix. Hard-to-guess identifiers are obscurity, not access control. The server-side permission check is always mandatory.
✅ Recap & What’s Next
- Broken access control is the failure of the authorization check — the app knows who you are but fails to verify you may access this resource or action; it’s among the most common and damaging categories.
- It takes many forms — IDOR, horizontal and vertical failures, forced browsing, missing function-level checks, parameter tampering — all sharing one root: no proper server-side permission check.
- The fix is to enforce every check server-side, verify ownership not just identity, and deny by default (Phase 4.2/4.3) — and ethically, you prove these bugs with the absolute minimum and never pillage real data.
Next (2.8): We look at attacks that abuse trust in requests themselves — tricking a browser, or a server, into making requests it never should: CSRF and SSRF.
⁂ Back to all modules