Skip to content

IDOR & access control

Broken Access Control is, year after year, the #1 risk in the OWASP Top 10. It happens when the app fails to verify that the user is allowed to perform the action or reach the resource they request. IDOR (Insecure Direct Object Reference) is its best-known variant: a tamperable identifier (id, uuid, filename, key) and the server doesn’t check that the object belongs to the requester.

GET /api/invoices/1001 -> your invoice
GET /api/invoices/1002 -> someone else's? (IDOR)

It needs no payloads: just change a value. That makes it trivial to exploit and, at the same time, one of the most frequent and high-impact flaws, especially in APIs.

Read/modify/delete other users’ data, access to admin functions, account takeover (a write IDOR on someone else’s email is usually direct ATO) and mass leakage if the endpoint allows enumeration.

The root cause is trusting the client: hiding options in the UI, validating only on the front end, or assuming “if they know the ID, it’s theirs”. There are two distinct checks and both must be server-side, per request:

  • Authentication: who are you? (session identity).
  • Authorization: can you do this action with this object? (role + ownership).
  • Horizontal: access another same-level user’s resources (classic IDOR; in OWASP API = BOLA, Broken Object Level Authorization).
  • Vertical: reach a higher role’s functions (normal user → /admin; in OWASP API = BFLA, Broken Function Level Authorization).
  • Function-level: the UI hides the button, but the endpoint is still reachable; or the check is applied only at one step.
  • Mass assignment: send extra fields (role=admin) the binder accepts (see its own page).
  • Context/state-dependent: authorized in one flow state, not another.
  • Two-account method (A and B): do an action as A and replay it with B’s session, swapping the identifier to A’s. If B gets in, it’s IDOR. Repeat the other way and across roles (normal vs admin).
  • Look for identifiers everywhere: URL/path, query, body/JSON, headers, cookies, and inside the JWT (claims like user_id).
  • Try ID types: incremental (enumerable), leaked UUIDs in other responses/logs/emails, filenames, encoded IDs (base64/hash) that are still guessable.
  • Vertical escalation: hit admin routes/methods as a normal user; try alternate HTTP methods (PUT, PATCH, DELETE), “hidden” endpoints, old API versions (/v1 vs /v2) and debug panels.
  • Review multi-step flows: is authorization enforced at every step or only at the start?
  1. In Burp, authenticate as A and save a request to one of their resources.
  2. Swap the ID for B’s while keeping A’s session → do you reach B’s data?
  3. Automate with Autorize (or AuthMatrix): you configure a second user’s cookies/token and the extension replays each request with that session, flagging green/red where the control fails. It’s the fastest way to cover the whole app.
  • Mass enumeration with sequential IDs (in an engagement, prove it with a few; don’t dump millions).
  • GraphQL: nodes by global id; try accessing other users’ nodes.
  • File/blob/signature references (download URLs, document tokens).
  • Race conditions on ownership checks (see its page).

Burp Suite (Autorize, AuthMatrix), ffuf for controlled ID enumeration, and cross-session response comparison.

Leakage of all users’ data, modification/deletion, escalation to admin and ATO. Chainable with JWT (forge user_id), with enumeration (discover IDs) and with mass assignment (write privileged fields).

  • One account accessing many distinct objects in a short time (enumeration).
  • Access to objects the user doesn’t own (requires logging identity + object + decision to detect it).
  • Calls to admin endpoints by role-less accounts; 403→200 transitions after parameter tampering.

Application/API-gateway logs with identity + accessed object + authorization decision, and per-user metrics (unique objects accessed per minute).

  1. Server-side authorization on every request, bound to the session identity (never to client parameters).
  2. Deny by default: every resource/function denied unless explicitly allowed.
  3. Check object ownership (WHERE owner_id = :session) on top of role.
  4. Centralize authorization (one middleware/policy, RBAC/ABAC) with automated per-role tests; don’t scatter copy-pasted checks across controllers.
  5. Indirect/unguessable references (UUID v4) as an extra layer, never a substitute for the control.
  6. Against mass assignment: allow-list bindable fields (DTOs).

Review the attacker’s accesses, notify affected users per regulation (GDPR), fix the control (ownership + role), and deploy enumeration detection.

  • First American Financial (2019) — sequential document URLs exposed ~885 million financial records (broken access control / document IDOR).
  • USPS “Informed Visibility” (2018) — an API with broken access control exposed data of ~60 million users.
  • Parler (2021) — sequential post IDs allowed archiving practically all of the platform’s content.
  • Peloton, Experian, T-Mobile and numerous mobile apps — BOLA/IDOR in APIs is among the most frequent and best-paid bug-bounty findings.

Broken Access Control is A01 of the OWASP Top 10. CVEs in NVD (https://nvd.nist.gov/vuln/search) and GitHub Advisories (https://github.com/advisories).

  • Two-account method applied to read and write.
  • IDs tested in URL, body/JSON, headers and JWT.
  • ID types tested (sequential, leaked UUID, encoded).
  • Vertical escalation: admin endpoints/methods as a normal user.
  • Multi-step flows validated at every step.
  • Mass assignment tested (extra fields like role).
  • Impact proven by accessing your second account’s resource (without touching real third-party data).