IDOR Vulnerabilities: How to Find and Fix Insecure Direct Object References

IDOR is one of the simplest vulnerability classes to understand and one of the easiest to miss in testing, because it doesn't fail — it just returns the wrong person's data.

Computer screen displaying web code
Photo by Mohammad Rahmani on Unsplash

An insecure direct object reference happens when an application uses a value the user can see or guess — an order number, a document ID, a user ID in a URL — to look up a resource, without separately checking whether the requesting user is actually authorized to access that specific resource. It’s conceptually simple, which is exactly why it’s easy to miss: the request doesn’t fail, doesn’t throw an error, and doesn’t look different from a legitimate one. It just returns data belonging to someone else.

Why standard testing misses it

Functional testing confirms a feature works for the user it was built for. Security testing that stops at authentication confirms a user can log in and reach the feature at all. Neither one, by default, asks “what happens if this same authenticated user changes the ID in the request to something that belongs to someone else” — which is the entire test that actually catches IDOR. It has to be added deliberately as its own test category, per endpoint that accepts an identifier, rather than assumed to be covered by general functional or auth testing.

How to actually test for it

The direct method: authenticate as two different test accounts, and for every endpoint that accepts a resource identifier, attempt to access account A’s resources while authenticated as account B. If the request succeeds and returns account A’s data, that endpoint is vulnerable. This needs to be done systematically across every endpoint that takes an ID — read endpoints, update endpoints, and delete endpoints separately, since an application can correctly restrict reads while still allowing an unauthorized update or delete through the same missing check.

Pay particular attention to endpoints that were added later, or that support secondary features (exports, attachments, comments, activity logs tied to a parent resource) — these are common blind spots because they weren’t part of the original access-control design review, if one happened at all.

Sequential IDs make exploitation trivial, but aren’t the root cause

Switching from sequential numeric IDs to UUIDs is a common recommendation, and it does raise the bar for casual exploitation — an attacker can’t simply increment a number to enumerate other users’ resources. But this is obfuscation, not a fix. If the authorization check is still missing, a UUID-based system is still vulnerable to anyone who obtains a valid UUID through another channel (a shared link, a referrer header, a leaked log entry, or simple correlation from other exposed data). Treat ID format as a hardening measure that raises exploitation cost, not as the actual control that closes the vulnerability.

The actual fix: authorize the object, not just the request

Every code path that fetches a resource by ID needs to verify, as an explicit step, that the currently authenticated user has a legitimate relationship to that specific resource — they own it, it was shared with them, or their role grants access to it — before returning or modifying it. This check has to happen server-side, on every request, independent of anything the client sends about what it thinks it’s allowed to do.

A useful pattern is to make this check structurally hard to skip: instead of writing “fetch the resource, then check authorization” as two separate steps a developer could forget to pair, write data-access functions that require the authenticated user’s identity as an input and only ever return resources that user is authorized for — moving the check into the data layer itself rather than leaving it to be remembered in every route handler.

A practical checklist

  • Test every ID-accepting endpoint with a second account’s credentials against the first account’s resources, across read, update, and delete separately
  • Don’t treat UUIDs as a fix — they raise exploitation difficulty, not close the gap
  • Add explicit object-level authorization at the data-access layer, not scattered per-route
  • Specifically review secondary and recently-added endpoints, since these are the most common places the check gets missed
  • Include authorization testing as its own category in your test suite, not folded into general functional testing

IDOR is one of the few vulnerability classes where the fix isn’t more complex code — it’s one consistent check, applied everywhere it needs to be, instead of assumed to already be there.

AppSecAPI Security