Lesson 3 · Reading · 30 min
IDOR and object-level access
You met this in Foundations as changing one number in a URL. In real applications it wears many disguises, hides in places that are not the URL, and is still, after two decades, one of the most common and most valuable web findings. This lesson is IDOR in full.
The shape of the bug
An application stores objects, orders, invoices, messages, profiles, files, and gives each one an identifier. When you ask for one, that identifier travels in your request, and the server uses it to look the object up:
GET /api/invoices/1042 → SELECT * FROM invoices WHERE id = 1042
The bug is what is missing from that query. A correct server also asks "and does invoice 1042 belong to the user making this request?" A vulnerable one does not, it trusts that because you sent 1042, you are allowed 1042. So you send 1041, and you get someone else's invoice. The reference was treated as proof of ownership, and a reference is not proof of anything: it is just a number you can change.
This is IDOR (Insecure Direct Object Reference), or in API terms BOLA (Broken Object Level Authorization). It is object-level authorization, failing.
Where the reference hides
Beginners look only in the URL path. References travel in far more places, and each is worth checking with the two-account method:
- Path segments:
/orders/1001,/users/87/profile. - Query parameters:
?invoice=1042,?file=report_1042.pdf,?account=87. - Request bodies: a JSON field like
{"orderId": 1001}in a POST or PATCH. - Headers and cookies: an
X-Account-Idheader, or an id baked into a cookie. - Hidden form fields: a
<input type="hidden" name="user_id" value="87">the page submits for you.
The test is identical everywhere: as Bob, put Alice's id where yours was, and see whose data comes back.
When the id is not a tidy number
Developers sometimes swap sequential integers for UUIDs (a3f1c...) or random strings, thinking that makes IDOR impossible. It does not, it makes it harder to guess the next id, but the authorization check is still missing. If you can obtain another user's UUID, the bug is fully alive. And UUIDs leak constantly: in a shared link, a notification, a "members" list, an API response that returns other users' ids, an earlier endpoint that hands them out. So the workflow becomes: find where the application discloses another object's id, then feed that id to the endpoint that does not check ownership. Guessability is a convenience, not the vulnerability; the missing check is the vulnerability.
Predictable ids make it worse, not different. Sequential integers let you enumerate, walk 1000, 1001, 1002… and harvest every record. That turns one leaked object into a full database dump, which is why a "small" IDOR on a sequential id is often a critical finding.
Reading versus writing
Always test both directions:
- Read IDOR: Bob fetches Alice's object. Data disclosure.
- Write IDOR: Bob modifies or deletes Alice's object by sending her id in an update or delete request. Often higher impact, you are not just reading a stranger's data, you are changing it.
Send Alice's update request with Bob's session and Alice's object id, and see whether the change takes.
The honesty gate for IDOR specifically
This is the bug where beginners most often overclaim, so be precise:
- Reaching your own object by its id is not a finding.
- Reaching data that is deliberately public (a public profile, a published post) is not a finding, even if it has an id.
- The finding is reaching another user's private object, and you prove it with two accounts you own: Bob reaches Alice's, both yours. You never confirm with a real stranger's record, and you never enumerate a live database of real people to "measure impact", proving one cross-account read establishes the flaw; the scale you describe from the id scheme, not by actually pulling thousands of real records.
Your lab
The attached lab, DriveShare booking IDOR, is a real application-shaped version of the Foundations order bug: a booking platform where a booking's id in the request is trusted, and one account can reach another's booking. Bring the two-account method. Register or use the two identities the lab gives you, find the booking reference in the request, and replay it as the other user. When another account's booking comes back, you have reproduced IDOR in something that behaves like production, the exact skill an exam's "identify and exploit" objective means.
Where this goes next
Object-level access is one half of broken authorization. The next lesson is the other half: function-level access, where the question is not "whose object is this?" but "is my role even allowed to call this function?", and where hiding a menu item is mistaken for enforcing a rule.
This lesson has 2 labs to try what you just read.