Insecure Direct Object Reference, usually shortened to IDOR, is one of the most common vulnerabilities I find, and one of the least technical to actually test for. Worth a full teardown, because understanding exactly why it happens makes it much easier to spot in your own app.
What it actually is
Almost every app refers to specific pieces of data by an identifier: an invoice number, a document ID, an order number, often visible directly in the URL or in a request the app makes behind the scenes. If the app fetches whatever record matches that identifier without also checking that the identifier belongs to the person asking for it, changing that number is enough to see someone else's data.
A concrete walkthrough
Say you're logged into an app and viewing your own order at a URL ending in /orders/1024. The app looks up order 1024, and shows it to you. Now change the number to /orders/1023. If the app's only logic is "find order 1023, show it," with no step that checks whether order 1023 actually belongs to whoever is logged in right now, it shows you someone else's order, in full, without any error or warning.
Why this is so easy to build by accident
The code that fetches "an order by its ID" is simple, and simple code is exactly what gets written first, and often works perfectly in early testing, because the person testing it is usually only looking at their own data and has no reason to try someone else's ID. The missing step, confirming ownership before returning the data, doesn't announce itself as missing. Everything just looks like it's working.
How to actually test for it
- Create two separate test accounts.
- Log in as the first account, find any URL or request containing a visible ID number tied to something that belongs to that account.
- Log in as the second account, and try changing that ID to something that belongs to the first account.
- If you can see, edit, or delete it, that's an IDOR, confirmed directly, regardless of how it's described elsewhere.
- Repeat across different types of data in the app, invoices, messages, documents, since a fix in one place doesn't guarantee the same check exists everywhere else.
The fix
Every place data is fetched by an ID needs an explicit ownership check as part of that same operation, not a separate step that can be skipped, confirming the requester actually has a right to that specific record before returning anything. It's a small, repeatable fix once you know to look for it, and exactly the kind of gap that's invisible until someone deliberately tries the wrong number.