← Tous les articles
pentestsecurityguide

IDOR explained: the bug that quietly leaks your users' data

What IDOR (Insecure Direct Object Reference) is, a plain example, why it's so common in fast-built apps, and how to prevent and test for it before launch.

FM
Frederick Marinho14 juin 2026 · 6 min de lecture

Change one number in a URL. That's the whole attack. IDOR — Insecure Direct Object Reference — is one of the most common, most boring, and most damaging bugs on the web, and it shows up constantly in apps that were built fast.

It's boring because there's nothing clever about it. There's no buffer overflow, no exotic payload, no cryptography. A user just asks for a record that isn't theirs, and the app hands it over because nobody checked whether they were allowed to have it. That's it. And because it's so simple, it's easy to ship without noticing — right up until someone notices for you.

What IDOR actually is

An IDOR happens when your app exposes a reference to an internal object — a database row, a file, a record — and trusts the value the user sends without verifying they're allowed to access that object.

"Direct object reference" sounds technical, but it just means an id that points straight at a thing. A user id, an invoice number, a document key. The "insecure" part is the missing check. Your code looks up the object by the id it was given and returns it, never asking the only question that matters: does the person making this request own this object, or have permission to see it?

When that check is missing, the id becomes a dial. The attacker turns it and reads through your data.

A concrete example

You build an invoicing app. A logged-in user views their invoice at a URL like /invoices/1043. The page loads correctly, the numbers are theirs, everything looks fine.

Now they change the URL to /invoices/1044. If your backend fetches invoice 1044 and renders it without checking who it belongs to, the user is now looking at someone else's invoice — their name, their amounts, maybe their address and tax id. Decrement to 1042, increment to 1045, and you can walk the entire table one number at a time. A short script does it in seconds.

It's rarely just reading. The same flaw on a "save" or "delete" endpoint means an attacker can edit or destroy records that aren't theirs. /api/users/88/email with a PATCH request, pointed at an id that isn't yours, and you've changed a stranger's account. The id was never the security boundary. The check was supposed to be — and it wasn't there.

Why it's so common in fast-built apps

IDOR thrives in exactly the conditions founders build under: move fast, ship the happy path, fix the rest later.

The root cause is trusting the client. When you're building quickly, you test as yourself, logged into your own account, looking at your own data. Every id you touch is one you're allowed to touch, so the missing ownership check never reveals itself. The app works perfectly in the only scenario you ever try.

It's also a natural consequence of how ORMs and quick CRUD scaffolding work. "Find the record by id and return it" is the obvious one-line query, and it's complete from a functionality standpoint. The authorization step — "and only if it belongs to the current user" — is a separate thought you have to remember to add on every single endpoint. Miss it once, on one route, and you have an IDOR. Frameworks rarely force you to write it, so it's the easiest thing in the world to forget.

Horizontal versus vertical

Access-control bugs come in two flavors, and IDOR is usually the horizontal kind.

Horizontal access means reaching data belonging to another user at your same privilege level. You're a regular customer; you read another regular customer's invoice. Same role, different owner. This is the classic /invoices/1044 case.

Vertical access means reaching functionality above your privilege level. You're a regular user, and you hit /admin/users or call an endpoint that only an admin should be able to call — and it works, because the app checks that you're logged in but not what you're allowed to do. You've moved up the hierarchy instead of sideways.

The fix mindset is the same for both: the server has to decide, on every request, whether this specific user is allowed this specific action on this specific object. Both failures come from skipping that decision.

How to prevent it

There's no magic library. Prevention is a discipline applied consistently, and a few principles cover most of it.

Authorize every object, every time. On every endpoint that touches a specific record, check that the current authenticated user is allowed to act on that record. Not just "are they logged in" — "do they own this, or have a role that grants access to it." This check belongs on reads, writes, and deletes alike.

Scope queries to the user. The most reliable pattern is to never look an object up by id alone. Instead of fetching invoice 1044 and then checking ownership, fetch "invoice 1044 belonging to the current user." If it isn't theirs, the query returns nothing and there's no object to leak. Ownership becomes part of the lookup rather than an afterthought you might forget.

Don't rely on unguessable ids. Swapping sequential integers for random UUIDs feels like a fix. It isn't. It's obscurity, not authorization. Ids leak — through referer headers, shared links, browser history, logs, a teammate's screenshot. The moment one valid id escapes, an unguessable identifier with no ownership check is just as exposed as a sequential one. Use UUIDs if you like, but never as the security boundary.

For the wider context, IDOR sits inside the broken access control category that tops the OWASP Top 10, explained — worth reading to see how it relates to the other failures you'll want to avoid.

How to test for it before launch

You can find most IDORs yourself with two accounts and some patience. Create user A and user B. Log in as A, capture a request that fetches one of A's objects, then replay it while authenticated as B. If B gets A's data, you have an IDOR. Repeat across your endpoints — every route that takes an id is a candidate — and don't forget the write and delete paths, not just reads.

Doing that by hand across a real app is tedious and easy to do incompletely, which is exactly the kind of broad, repetitive checking that automation handles well. Kalit Pentest runs about twelve specialist agents in parallel that probe your endpoints for access-control flaws like this, then report each finding with a CVSS severity, the actual request and response that proves it, and a remediation telling you how to close it. It's non-destructive and only tests targets you authorize, so you can run it before launch and again after every fix in minutes.

Recap

IDOR is simple to understand and simple to prevent once you know to look for it.

  1. IDOR is reading or editing someone else's record by changing an id the app trusts without checking ownership.
  2. The classic case is /invoices/1043 becoming /invoices/1044 and the data loading anyway.
  3. It's everywhere in fast-built apps because you test as yourself and the ownership check is easy to forget.
  4. Horizontal access reaches a peer's data; vertical access reaches privileges above you. Both come from a skipped authorization decision.
  5. Fix it by authorizing every object on every request, scoping queries to the current user, and never treating unguessable ids as a substitute for a real check.

The bug is quiet because it never crashes anything — it just hands the wrong data to the wrong person. Find it before someone else does.