Skip to content

IDOR

IDOR is one bug masquerading as two: the app exposes a direct handle to a resource (?file_id=123, /api/profile/74, ?contract=<md5>), and the back-end doesn’t check whether the caller is authorized to use that handle. Substitute someone else’s identifier and you get their data - or perform actions in their name.

# Identify a direct reference in a URL, body, or header
?file_id=123 /api/profile/74 Cookie: uid=1 contract=cdd9...
# Substitute and observe
curl 'http://target/download.php?file_id=124' # someone else's file?
curl 'http://target/api/profile/75' # someone else's profile?
# Mass-enumerate when the pattern is sequential
for i in $(seq 1 1000); do
curl -s "http://target/api/profile/$i" >> all-profiles.json
done
# When the handle is hashed or encoded, find the algorithm
echo -n '1' | base64 | md5sum # often the answer is in the front-end JS

Success indicator: a resource (file, record, action result) you should not have access to comes back as if it were yours.

Two ingredients, both required:

  1. Direct object reference - a handle in a request that names a specific resource (file, record, account, action). The handle is in client-controlled space: URL parameter, path segment, header, cookie, body field.
  2. Missing access control - the server treats the handle as authoritative, looking up the resource without first checking “is the caller allowed to touch this resource?”

The “Insecure” in IDOR modifies the access control, not the reference. Direct object references are normal and necessary - the bug is the missing authorization layer. A perfectly secure app can use sequential integer IDs in its URLs, provided every request hits an access-control check that compares the caller’s identity to the resource’s ACL.

Apps fail this in three common ways:

Failure Example
No access control at all /api/profile/74 returns user 74’s data to anyone who asks
Access control only in the UI The UI hides the “edit other users” button; the back-end happily processes the request if you send it
Access control only on some verbs GET /api/profile/74 checks; PUT /api/profile/74 doesn’t - see Verb tampering

The operator should distinguish these because the impact and the exploitation workflow are different:

Flavor What it does Operator workflow
Information disclosure Read resources you shouldn’t see - files, records, profile fields, internal state Substitute the reference, dump the response, iterate to enumerate everything
Insecure function call Perform actions you shouldn’t be able to - change someone else’s password, delete records, escalate role Substitute the reference and construct a request to the action endpoint; chain with disclosure to learn required parameters (other user’s uuid, role names, etc.)

The two often chain: disclosure leaks the values you need (the victim’s uuid, the admin role name, the file hash format) to invoke the function call.

Look in this order - the first three account for most findings:

Location Example Notes
URL query ?uid=1, ?file_id=123 Most common; visible in browser; easiest to fuzz
URL path /users/74, /files/abc123.pdf REST APIs favor this; same risk
Request body {"uid": 74, "uuid": "40f5..."} API mutations; check both the JSON keys and hidden form fields
Header Cookie: uid=1, X-User-Id: 1, Authorization: Bearer ... Sometimes the only auth signal; the bearer token, if not bound to the resource, lets any logged-in user read anyone’s data
AJAX call in JS fetch('/api/admin/users') triggered only when role=admin Read the front-end JS - find function-call references the server might accept from any session

The front-end JS angle deserves emphasis. Many SPAs put the whole function catalog in JS and gate each call by role on the client. Read the JS, find the admin-only function:

function changeUserPassword() {
$.ajax({
url: "change_password.php",
type: "post",
data: {uid: user.uid, password: user.password, is_admin: is_admin},
...
});
}

The JS only invokes this when the user is admin. But the back-end endpoint accepts requests from anyone - that’s the IDOR. The discovery method is reading the bundle, not poking the UI.

Building an access-control system is a separate cross-cutting concern that has to be designed in from the start. RBAC (role-based access control), ABAC (attribute-based), or per-row ACLs are non-trivial to retrofit. Apps that grow organically tend to accumulate authentication (knowing who you are) without authorization (knowing what you’re allowed to do at the resource level).

The result: IDOR is one of the most common findings in modern web apps, including Facebook, Instagram, and Twitter.

Severity Example
Low Disclosure of public-ish data (other users’ display names, profile pictures already shared elsewhere)
Medium Disclosure of internal data (other users’ email addresses, internal IDs, account creation dates)
High Disclosure of sensitive personal data (credit cards, SSNs, addresses, private messages, uploaded documents)
Critical Account takeover (change another user’s password, email, recovery token); privilege escalation (set your own role to admin); mass data manipulation

The “Critical” tier is where chained IDORs land. Information-disclosure IDOR + function-call IDOR + role-name leak together = unauthenticated admin compromise.

Page Focus
Identifying Where to find direct object references, AJAX-call discovery, hash/encoding inspection, comparing roles across test accounts
Mass enumeration Burp Intruder / ffuf / scripted bulk pulls; reversing MD5-of-base64-style “hashed” references
Insecure APIs REST verb-by-verb attack surface (PUT/POST/DELETE on user objects), uid-mismatch / uuid-mismatch defense bypass, role-field manipulation
GraphQL Introspection mapping, direct id-argument access, Relay global-ID decoding, alias/batch mass-enumeration in one request, mutation IDOR
Chaining End-to-end: GET disclosure → uuid harvest → PUT modification → role escalation → admin function calls
Defense Server-side object-level authorization on every request/verb, identity from the session not the request, unpredictable references as defence in depth
Topic Page
Verb-tampering bypasses (auth scoped to GET/POST only) Verb tampering
Session attacks that often follow IDOR disclosure Sessions cluster
When a leaked file URL points to disk content LFI cluster
XSS payloads delivered via IDOR-modified profile fields XSS to session
Password reset flow chained with IDOR-modified email Reset tokens
XXE for back-end file disclosure after IDOR foothold XXE cluster