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.

··6 min read·By ismycodesafe.com Security Team
Two requests differing only by ID: id=123 returns your invoice, id=124 returns another user's invoice because the server never checked ownership

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.

Vulnerable code fetches an invoice by ID alone; fixed code fetches by ID scoped to the current user's ownership
The fix is one extra condition: scope every lookup to the current user.
  • 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.

Scan My Website — Free

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.