IDOR: How Changing One Number Leaks Another User's Data
Insecure Direct Object Reference is one of the most common and most exploitable bugs on the web. Here is a worked example, why it counts as broken access control, and the fix that actually closes it.
Key Takeaway
IDOR (Insecure Direct Object Reference) lets an attacker access another user's data by changing a predictable ID in a request (e.g. /invoice?id=123 to 124). It is a form of broken access control. Fix it with a server-side authorization check on every object access, not with unguessable IDs.
What Is IDOR?
IDOR stands for Insecure Direct Object Reference. It lets an attacker access another user's data by changing a predictable identifier in a request, for example switching /invoice?id=123 to /invoice?id=124. The server hands over the second invoice because it checked that the ID exists, not whether the person asking is allowed to see it. That missing check is the entire vulnerability.
It is unglamorous and it is everywhere. No special payload, no encoding tricks. You change a number in a URL or a request body and see if you get back something that is not yours. That low skill floor is exactly why it shows up so often in bug bounty reports.
A Worked Example
Say a billing page loads your invoice from a URL like /invoice?id=123. The handler looks up invoice 123 and renders it. Now an attacker, logged in to their own account, edits the URL to id=124.
// The vulnerable handler
app.get('/invoice', (req, res) => {
const invoice = db.invoice.find(req.query.id); // trusts the ID
res.json(invoice); // returns whatever it found
});
// Attacker is logged in as user A, requests invoice 124 (user B's).
// The server never asks "does this user own invoice 124?"
// It just returns it.If invoices are numbered sequentially, the attacker can walk the whole range, 1, 2, 3, and scrape every invoice in the system. Same pattern hits /api/users/124, /orders/124, /messages/124, anywhere an object is fetched by an ID that came from the request.
Why It Is Broken Access Control
IDOR is the textbook case of broken access control, which sits at A01, the top of the OWASP Top 10 (2021). Access control is the rule that says this user may see that object. IDOR happens when the rule is either missing or enforced only in the UI. Hiding the link to invoice 124 in the front-end does nothing; the attacker calls the endpoint directly. Authorization that lives only in the client is not authorization.
How to Fix IDOR
The fix is a server-side authorization check on every single object access. Confirm the current user is allowed to touch the object before you return it. The most direct version folds the owner into the query itself, so a mismatched user simply gets nothing back.
- Authorize per request. Check ownership or role on every object access, in the handler, not in the template.
- Scope the query to the user.
find({ id, ownerId: req.user.id })returns nothing for objects the user does not own, which fails closed by default. - Use indirect reference maps where it fits, mapping per-session handles (item 1, 2, 3 for this user) to real database IDs, so the real IDs are never exposed.
- Centralize the check. A policy layer or middleware that every data access passes through beats scattering
if (owner)checks you will forget on the next endpoint.
Do UUIDs Fix It?
No, and this is the trap. Swapping sequential IDs for UUIDs makes them hard to guess, so the casual walk-the-range attack stops working. But the bug is the missing authorization check, not the shape of the ID. UUIDs leak, in logs, in referrer headers, in shared links, and the moment one does, an endpoint with no ownership check is fully exploitable again. Treat unguessable IDs as a small speed bump, never as the fix.
Test Your Site
IDOR is a logic flaw, so it needs request-level testing: take an endpoint that returns an object by ID, log in as a low-privilege user, and ask for an object you should not be able to see. If it comes back, you have IDOR. We built ismycodesafe because this is the kind of bug AI-assisted code reproduces constantly, the model writes the happy-path lookup and quietly skips the ownership check, since the prompt rarely asks for it.
Check your website right now
110 security checks in 60 seconds. Free, no signup required.
Scan My Website (Free)ismycodesafe.com Security Team
We run automated security scans on thousands of websites daily, combining static analysis, SSL/TLS inspection, header auditing, and CVE lookups. Our team tracks OWASP, NIST, and evolving compliance requirements (GDPR, NIS2, PCI DSS) to keep these guides accurate and practical.
Related Articles
OWASP Top 10: Every Vulnerability Explained
IDOR is the textbook case of A01: Broken Access Control, the number one risk on the list.
Cross-Site Scripting (XSS): How Attackers Steal User Data
The other half of most account-takeover chains. Stored, reflected, and DOM-based XSS.
SQL Injection Is Still the #1 Database Threat
Another input-trust failure, one layer down at the query.
Pre-Launch Security Checklist
Authorization checks on every object access belong on this list before you ship.