What is an IDOR vulnerability?
An Insecure Direct Object Reference occurs when an application uses a user-supplied input — an ID, filename, username, or other identifier — to access a resource, without verifying that the requesting user is authorized to access that specific resource.
The vulnerability isn't in how the object is referenced. It's in the missing authorization check. The application trusts the client to send a valid ID for resources they own. An attacker simply sends someone else's ID.
IDOR falls under OWASP A01:2021 — Broken Access Control, the top-ranked web application risk category.
The classic HTTP proof of concept
Here is a minimal IDOR example. An authenticated user with account ID 1042 fetches their own order:
GET /api/orders/1042 HTTP/1.1
Host: app.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 1042,
"user_id": 58,
"total": 249.00,
"items": [...]
}The attacker changes the ID to 1041:
GET /api/orders/1041 HTTP/1.1
Host: app.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... ← attacker's own token
HTTP/1.1 200 OK ← should be 403 Forbidden
Content-Type: application/json
{
"id": 1041,
"user_id": 57, ← different user
"email": "victim@email.com",
"total": 189.00,
"items": [...] ← victim's order data exposed
}The application returned another user's data using the attacker's authentication token. The authorization check was missing entirely — the server only verified that someone was authenticated, not that they owned order 1041.
IDOR attack surface — where to look
IDORs appear anywhere an application exposes object references to clients. Common locations:
REST API path parameters
/api/users/842/profile, /api/invoices/3891
Query string parameters
GET /documents?id=552, GET /export?user=91
POST / PUT body fields
{"account_id": 182, "action": "transfer"}File download endpoints
/files/report_user_58_q3.pdf
Account settings / preferences
PATCH /settings/notifications with user_id in body
Admin functions with user-supplied target
POST /admin/impersonate with user_id
Horizontal vs vertical privilege escalation
IDOR vulnerabilities enable two types of unauthorized access:
Horizontal escalation
User A accesses User B's data. Same privilege level, different account. Example: viewing another customer's invoices, reading another user's private messages.
Vertical escalation
A regular user accesses an admin resource. Example: hitting /api/admin/users/list with a standard user token, or invoking an admin action endpoint by guessing its ID.
CVSS 3.1 scoring
A typical IDOR giving read access to another user's data scores:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N Base Score: 6.5 (MEDIUM → HIGH depending on data sensitivity) Attack Vector: Network (exploitable remotely) Attack Complexity: Low (no special conditions) Privileges Required: Low (authenticated user) User Interaction: None (no victim action needed) Scope: Unchanged Confidentiality: High (full object data exposed) Integrity: None (read-only in this example) Availability: None
An IDOR that also allows writes (modifying another user's data, deleting records) scores Integrity: High, pushing the score to 8.1 (High) or above.
Testing methodology
Step 1: Map all object references
Using a proxy (Burp Suite, OWASP ZAP, or browser DevTools), intercept all requests while using the application as a normal user. Capture every request that contains an ID-like parameter — numeric IDs, UUIDs, slugs, filenames.
Step 2: Create two test accounts
Register two separate accounts (Account A and Account B). Log all requests from Account A, noting every object ID that appears. These are your test candidates.
Step 3: Replay with Account B's credentials
For each request captured from Account A, replay it using Account B's authorization token, substituting Account A's object IDs:
# Original request from Account A GET /api/profile/reports/77 HTTP/1.1 Authorization: Bearer <account_A_token> # Test: replay with Account B's token, keeping Account A's resource ID GET /api/profile/reports/77 HTTP/1.1 Authorization: Bearer <account_B_token> # Vulnerable if response is 200 with Account A's data # Correct behavior: 403 Forbidden or 404 Not Found
Step 4: Test numeric enumeration
For sequential numeric IDs, test ±1 and ±10 relative to your known IDs. Also test boundary values (0, 1, -1, very large numbers).
Note on UUIDs
UUIDs make enumeration harder but don't prevent IDOR. If UUID-identified resources are referenced in other API responses, emails, or shared links, an attacker can still obtain valid UUIDs and exploit missing authorization checks.
Remediation
Server-side authorization on every request
The fix is not obscuring IDs. The fix is checking on the server, for every resource access, that the authenticated user owns or has permission to access the requested object. This check must happen in the business logic layer, not just at the route middleware level.
Never use client-provided IDs as implicit authorization
The pattern 'user sends ID, server returns that resource' is inherently unsafe. The correct pattern: 'server looks up resources owned by the authenticated user, then filters by ID'. The user ID comes from the validated session token, not from the request body.
Per-user indirect references
Map publicly-visible IDs to internal IDs on the server. User sees order reference 'REF-7823', server maps this to internal ID 1041 for that specific user's session. Even if another user submits 'REF-7823', the server resolves it within the context of their session and finds nothing.
Automated regression tests
Add authorization tests to your CI pipeline: for every sensitive endpoint, verify that a valid token for User B cannot access resources belonging to User A. These tests catch regressions when new endpoints are added without authorization checks.