IDOR Testing: Finding Insecure Direct Object Reference Vulnerabilities

Insecure Direct Object Reference (IDOR) consistently ranks among the highest-impact vulnerabilities in real-world bug bounty programs and penetration tests. The mechanism is deceptively simple: an application exposes a reference to an internal object — a database row ID, a filename, an account number — and fails to verify that the requesting user is actually authorised to access it. The attacker just changes the number.

Despite its apparent simplicity, IDOR has led to some of the most severe data breaches in recent years. Medical records, financial transactions, private messages, and PII have all been exposed at scale through nothing more than parameter manipulation. This guide covers how to systematically find, confirm, and demonstrate IDOR vulnerabilities across modern web applications and APIs.

What Makes an IDOR Exploitable

IDOR is a subset of broken access control (OWASP A01). Three conditions must be true simultaneously for exploitation:

  1. A reference is exposed to the client. The application passes an object identifier — an integer, UUID, hash, or filename — in a URL parameter, request body, cookie, or header.
  2. The reference maps directly to a back-end object. The identifier corresponds to a database row, file path, or resource without an indirection layer.
  3. Access control is absent or insufficient. The server does not verify that the authenticated session owns or is authorised to act on the referenced object.

The root cause is almost always that developers implement authentication (verifying who you are) but skip authorisation (verifying what you are allowed to do). The session token is validated, but the resource ownership check is missing entirely.

Horizontal vs. Vertical Privilege Escalation

IDOR vulnerabilities fall into two escalation categories, and distinguishing them matters for both impact assessment and reporting.

Horizontal privilege escalation occurs when a user accesses resources belonging to another user of the same privilege level. User A reads User B's invoices. Neither has elevated permissions — the flaw is that A can impersonate B at the data layer. This is the most common IDOR pattern.

Vertical privilege escalation occurs when a lower-privileged user accesses resources or functions reserved for a higher-privileged role. A standard user calls an admin endpoint by guessing or knowing its object ID. This is less common but critically severe — it often leads to full application compromise.

Type Example Typical Impact CVSS Range
Horizontal read GET /api/invoices/1042 returns another user's invoice Data exposure, PII leak 6.5 – 7.5
Horizontal write PUT /api/profile/1042 modifies another user's profile Account takeover, data manipulation 7.5 – 8.5
Horizontal delete DELETE /api/posts/1042 removes another user's content Data destruction, DoS 7.0 – 8.0
Vertical read GET /api/admin/users/1042 accessible by non-admin Mass PII exposure, privilege escalation 8.0 – 9.0
Vertical write POST /api/admin/config accessible by non-admin Full application compromise 9.0 – 10.0

Reconnaissance: Finding Object References

Before you can test for IDOR, you need to map every surface where the application exposes object identifiers. This requires thorough proxied browsing across all authenticated user flows.

Set up Burp Suite with your browser proxied through it and authenticate as a test user. Walk through every feature: viewing records, updating settings, uploading files, downloading reports, sending messages. Every HTTP interaction is captured. Look for identifiers in:

  • URL path segments: /api/orders/8821
  • Query string parameters: ?user_id=8821&report=Q1
  • JSON request bodies: {"account_id": 8821}
  • JSON response bodies (IDs returned that can be replayed)
  • Cookie values: session_context=user:8821
  • Custom headers: X-User-Id: 8821
  • File download paths: /files/invoices/8821_Q1.pdf
  • WebSocket messages

Use Burp's search across all HTTP history to find numeric patterns and UUID patterns. The Param Miner extension can surface hidden parameters not visible in the normal UI that still influence server-side object lookups.

Testing Integer-Based and Sequential IDORs

Sequential integer IDs are the most straightforward case. If your authenticated session has access to /api/documents/4521, attempt to retrieve /api/documents/4520 and /api/documents/4522 using a second test account's session token to confirm ownership.

The two-account testing methodology is essential for clean proof of concept:

# Account A session token
TOKEN_A="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo0NTIxfQ..."

# Account B session token
TOKEN_B="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo0NTIyfQ..."

# Step 1: Confirm Account A owns document 4521
curl -s -H "Authorization: Bearer $TOKEN_A" \
  https://target.example.com/api/documents/4521 | jq '.owner_id'
# Returns: 4521

# Step 2: Attempt to access Account A's document using Account B's token
curl -s -o /tmp/idor_response.json -w "%{http_code}" \
  -H "Authorization: Bearer $TOKEN_B" \
  https://target.example.com/api/documents/4521

# Vulnerable: returns 200 with Account A's document data
# Secure:     returns 403 Forbidden or 404 Not Found

For bulk enumeration across a range of IDs, use ffuf or a simple Burp Intruder attack. Always throttle enumeration — rapid sequential requests are both detectable and potentially disruptive:

# Enumerate a range of document IDs with Account B's token
# -rate limits to 10 requests/second to avoid detection
ffuf -u "https://target.example.com/api/documents/FUZZ" \
  -H "Authorization: Bearer $TOKEN_B" \
  -H "Accept: application/json" \
  -w <(seq 4500 4600 | tr '\n' '\n') \
  -fc 403,404 \
  -rate 10 \
  -o idor_results.json \
  -of json

# Filter for successful hits
cat idor_results.json | jq '.results[] | select(.status == 200) | .input.FUZZ'

Pay attention to response differentials — a vulnerable endpoint may return a 200 with an empty body, a 200 with an error message in the JSON payload, or a redirect. Check all three, not just HTTP status codes.

UUID and Hash-Based IDORs

Many developers believe switching from sequential integers to UUIDs or hashes prevents IDOR. This is a dangerous misconception. UUIDs prevent guessing, but they do not prevent exploitation when an attacker can obtain a valid UUID through another channel — and in real applications, this happens constantly.

Common UUID disclosure vectors include:

  • Shared links or export URLs that contain another user's UUID embedded in the path
  • API responses that return other users' UUIDs in relational data (e.g., a task response that includes the creator_id UUID of its author)
  • Email notifications with links containing UUIDs
  • JavaScript source files hardcoding resource UUIDs in frontend logic
  • Version 1 UUIDs that embed a timestamp and MAC address — predictable with known timing data

Once you have a UUID from any of these vectors, the IDOR test is identical: make the request with a different session and observe the response. The fact that UUIDs are long does not add access control.

For hash-based references (e.g., MD5 of a username or account number), attempt to reverse common input patterns. echo -n "user4521" | md5sum is a trivial computation. If the application uses hashes of predictable inputs as object references, it provides only security through obscurity.

API Parameter Manipulation and Mass Assignment

Modern REST and GraphQL APIs surface additional IDOR vectors beyond simple path parameters. Parameter pollution and mass assignment deserve specific attention.

Parameter pollution involves injecting additional parameters into a request to override the server's object lookup. If an authenticated endpoint normally resolves the object from the session (GET /api/profile returns your own profile), look for whether adding a query or body parameter changes the lookup:

# Normal request — returns current user's profile
GET /api/profile
Authorization: Bearer $TOKEN_B

# Parameter pollution attempt — does this resolve a different user's profile?
GET /api/profile?user_id=4521
Authorization: Bearer $TOKEN_B

# Body parameter injection in a PUT request
PUT /api/profile
Authorization: Bearer $TOKEN_B
Content-Type: application/json

{
  "display_name": "Attacker",
  "user_id": 4521,
  "email": "victim@example.com"
}

# Also try nested parameter forms used by some frameworks
# ?filter[user_id]=4521
# ?userId=4521
# ?uid=4521
# X-User-ID: 4521 header

Mass assignment is a closely related vulnerability where an API endpoint binds request body fields directly to model attributes without filtering. If a user can send a JSON field that maps to a privileged column — such as role, is_admin, subscription_tier, or account_balance — they may be able to escalate privileges or modify data they should not own.

To test for mass assignment, review the API's response fields and attempt to echo them back as writable fields in update requests. If GET /api/user/me returns "role": "user", try submitting PATCH /api/user/me with {"role": "admin"} in the body and check whether the response or subsequent requests reflect the change.

IDOR in File Downloads and Document Exports

File download endpoints are a frequently overlooked IDOR surface. Applications that generate PDFs, exports, or serve user-uploaded files often use predictable or exposed identifiers in the download URL.

Common patterns to test:

  • /exports/report_4521_2026Q1.pdf — numeric ID in filename
  • /api/download?file=invoices/4521.pdf — path traversal may also be possible here
  • /api/export?job_id=a3f9c1d2 — async job ID from a prior response
  • /api/attachments/4521/receipt.png — composite path with user ID

For async export flows, the pattern is: trigger an export with Account A, capture the job ID from the response, then poll that job ID using Account B's session. Async jobs are particularly likely to be vulnerable because developers often focus access control on the trigger endpoint and forget to secure the retrieval endpoint.

Confirming and Documenting IDOR for Reports

A clean IDOR proof of concept requires demonstrating that a cross-account access actually occurred — not just that a 200 was returned. For a compelling report:

  1. Establish baseline ownership. Show that Account A owns the resource (its ID appears in Account A's session data or the resource was created by Account A).
  2. Show the successful unauthorised access. Capture the full HTTP request made with Account B's session token and the full response showing Account A's data.
  3. Demonstrate no legitimate access path exists. Confirm Account B has no business reason to access Account A's resource — different organisations, different roles, or the data is clearly personal.
  4. Assess the blast radius. How many records are accessible? Can enumeration be automated? Is sensitive PII or financial data exposed?

For the HTTP captures, use Burp's "Copy as curl command" on both the request and the response, and include them verbatim in your report. Avoid screenshots of browser developer tools — raw HTTP evidence is unambiguous and reproducible by the development team.

Common Defences and What Bypasses Them

Understanding the defences developers implement helps you test them effectively and explain what secure implementation looks like in your findings.

Server-side ownership checks are the correct fix: before returning or modifying any object, the application queries whether the authenticated user's session ID matches the object's owner field. This cannot be bypassed by parameter manipulation alone.

Indirect object references replace internal IDs with session-scoped tokens — instead of /invoices/4521, the application issues a one-time or session-scoped token like /invoices/tok_a8f3c1d9 that maps to the ID server-side. These are harder to enumerate but still vulnerable if the mapping is not session-scoped.

Rate limiting and anomaly detection slow down bulk enumeration but do not prevent individual IDOR requests. A single manually crafted request with a known ID is undetectable by rate limiting.

Security through obscurity — UUIDs, hashes, long random tokens — raises the bar for guessing but is not a substitute for access control. Any ID disclosed to an attacker through another vector is immediately exploitable.

Ironimo automatically tests every authenticated endpoint in your application for IDOR and broken access control vulnerabilities — running both horizontal and vertical privilege escalation checks across all discovered object references without manual effort. Every finding includes a full HTTP request/response pair and a severity assessment ready for your remediation backlog.

Stop relying on manual spot-checks for one of the most common high-severity vulnerability classes in modern web applications.

Start free scan
← Back to blog