Skip to content

Insecure direct object reference (IDOR)

CWE-639OWASP A01:2021Updated August 29, 20265 min read

An insecure direct object reference (IDOR) occurs when an application uses a user-supplied ID to fetch an object without checking that the logged-in user is actually allowed to access it. By simply changing invoice number 1042 to 1043, an attacker can read someone else's data.

An insecure direct object reference (IDOR) is a classic among web application and API vulnerabilities: technically simple, remarkably widespread, and often painful in its consequences. The application lets users point at objects (invoices, records, photos) directly by ID, but never verifies that the requester is allowed to access that particular object. Change the ID, and you are reading someone else’s data.

What is an insecure direct object reference?

An IDOR arises when an application exposes an internal reference (usually a database ID, sometimes a filename or customer number) directly in a URL, form, or API request, and then bases access solely on whether someone is logged in rather than on whether this user may access this specific object. Authentication is in place; object-level authorization is missing.

Think of a coat check without any verification: whoever calls out number 87 gets coat 87 handed over, even while holding ticket 12. The attendant does check that you have a ticket at all (authentication), but not whether the ticket matches the coat (authorization).

In the OWASP Top 10, IDOR falls under A01:2021 (Broken Access Control), the most prevalent category of vulnerabilities. In API security the same issue is commonly called BOLA: broken object level authorization. MITRE tracks it as CWE-639, “Authorization Bypass Through User-Controlled Key”.

How does an IDOR attack work?

An attacker usually needs nothing more than a legitimate account of their own. With it, they observe how the application addresses objects (say, GET /api/invoices/1042 for their own invoice) and then simply change the number to 1043, 1044, and so on. If the server responds with someone else’s invoice instead of an error, the IDOR is confirmed. With a small script, thousands of objects can be harvested per hour, especially when IDs are sequential and predictable.

The vulnerable reference does not have to live in the URL path. Query parameters, hidden form fields, JSON fields in a POST body, cookies, and download filenames are equally popular spots. Nor does it stop at reading: the same flaw in an update or delete endpoint lets an attacker modify or erase other people’s data.

A concrete example from a Node.js/Express API:

Vulnerable:

// GET /api/invoices/:id
app.get("/api/invoices/:id", requireLogin, async (req, res) => {
  // Fetches whatever invoice the URL names - no matter whose it is
  const invoice = await db.invoices.findById(req.params.id);
  if (!invoice) return res.status(404).end();
  res.json(invoice);
});

The requireLogin middleware dutifully checks that a session exists, but the query then fetches any ID it is given. The secure variant ties the lookup to the logged-in user:

Secure:

// GET /api/invoices/:id
app.get("/api/invoices/:id", requireLogin, async (req, res) => {
  // Only search within the logged-in user's own invoices
  const invoice = await db.invoices.findOne({
    id: req.params.id,
    ownerId: req.user.id,
  });
  if (!invoice) return res.status(404).end();
  res.json(invoice);
});

If the invoice exists but belongs to someone else, the requester now receives the same 404 as for a non-existent ID, so the application does not even reveal which numbers are in use. The same check belongs in every endpoint that reads, updates, or deletes an object.

What is the impact of an IDOR vulnerability?

Read access alone is serious: personal data, invoices, medical records, or contracts end up exposed. Because IDs often increment, such a leak scales effortlessly from a single record to the entire dataset. For organizations handling EU personal data, that quickly becomes a reportable breach under the GDPR, complete with regulators, customer notifications, and reputational damage. The best-known example is US title insurer First American, where in 2019 roughly 885 million documents containing mortgage and personal data turned out to be freely retrievable through sequential document numbers.

Write access makes things worse. An attacker who can edit another user’s profile can, for instance, change the victim’s email address and then take over the account through a password reset. That makes IDOR a common stepping stone to full account takeover and fraud.

Security professionals distinguish horizontal escalation (accessing data of peer users) from vertical escalation (reaching admin-level functions). IDOR findings therefore typically score medium to high, depending on the sensitivity of the data and whether write operations are possible.

How do you detect IDOR?

Automated scanners rarely find IDOR reliably: a scanner does not know which object belongs to which user, and a valid response looks exactly like a leak to a tool. Detecting IDOR is therefore largely manual work:

  • Test with two (or more) accounts: perform every action as user A, then replay the request with user B’s session and A’s IDs.
  • Systematically walk through every object reference: URL paths, query parameters, POST bodies, hidden fields, export and download links.
  • Use tooling such as Burp Suite (Repeater, or the Autorize extension) to replay requests with swapped sessions.
  • Watch for indirect routes as well: reporting features, PDF generators, search endpoints, and legacy API versions skip the check remarkably often.

In an AssistSec penetration test this is a standard part of the job: every object reference is replayed with multiple test accounts, precisely because this class of flaws escapes automated tooling. Detection is possible in production too: bursts of 403 or 404 responses across sequential IDs in your logs point to enumeration.

How do you prevent IDOR?

The cure is conceptually simple: on every object request, verify server-side that the requester is allowed to access that specific object.

  • Enforce object-level authorization on every read, write, and delete, never only at login.
  • Scope database queries to the owner or tenant by default (ownerId, organization ID), as in the example above.
  • Centralize the check in middleware or policy classes, so a forgotten check in one endpoint does not instantly become a leak.
  • Deny by default: no explicit permission means no access, answered with a neutral 404.
  • Replace predictable, sequential IDs with unpredictable ones (UUIDs) where possible, as an extra hurdle.
  • Add multi-user tests to CI: an integration test that requests user A’s objects with user B’s session prevents regressions.
  • Make authorization a fixed item in the code review of every new endpoint.
Unpredictable IDs such as UUIDs make guessing harder, but they do not fix the underlying problem: as soon as an ID leaks (through an email, a log file, or a referer header) the object is still open to anyone. The only real fix is an authorization check per object.

Sources

Frequently asked questions

Is IDOR the same as BOLA?

Essentially, yes. BOLA (broken object level authorization) is the term the OWASP API Security Top 10 uses for the same missing object-level authorization in APIs.

Do UUIDs protect against IDOR?

Only partially. Unpredictable IDs make guessing harder, but once an ID leaks the object is still accessible. Only a per-object authorization check truly fixes the flaw.

How dangerous is IDOR?

Typically medium to high. With sensitive data or write access involved, a single changed ID can lead to a large-scale data breach or an account takeover.

Can a scanner find IDOR automatically?

Rarely with any reliability. A scanner does not know which user owns which object; manual testing with multiple accounts remains the most effective approach.

Related articles

Press / to search · Esc