Insecure Direct Object Reference is consistently one of the most exploited vulnerabilities in web and API security. It is also consistently misunderstood, underestimated, and inadequately tested. This is a complete technical breakdown — from concept to exploitation to remediation.

Insecure Direct Object Reference — IDOR, or its newer name Broken Object Level Authorisation (BOLA) — is ranked number one in the OWASP API Security Top 10. It has been in the top rankings of web application vulnerabilities for over a decade. It is found in enterprise applications, government systems, healthcare platforms, banking portals, and startups alike. And it is, structurally, one of the simplest vulnerabilities to understand and one of the most consistently underestimated in both development and testing.

In this article, I want to explain exactly what IDOR/BOLA is, how it works in practice, what real-world exploitation looks like, how to find it as a penetration tester, and how to fix it as a developer. By the end, the vulnerability should feel less like a technical abstraction and more like the specific, concrete failure mode it actually is.

⚠️

None

Responsible disclosure note

All exploitation examples in this article use fictional or lab environments. The techniques are described for educational and defensive purposes — to help developers build more secure systems and to help security professionals test them more effectively. Never test systems you do not have explicit written permission to test.

— — —

1. What IDOR Actually Is — The Precise Definition

IDOR occurs when an application uses a user-controlled input — a number in a URL, a parameter in a request body, an identifier in a cookie — to directly access an object (a database record, a file, an account) without verifying that the authenticated user has permission to access that specific object.

The critical distinction: IDOR is not about authentication — the user is logged in. It is about authorisation — the application is not checking whether this logged-in user is allowed to access this specific resource. The check that is missing is the one that asks: does user A have permission to see user B's data?

# The vulnerable pattern — IDOR in a Python/Flask endpoint

# User is authenticated. But no authorisation check exists.

@app.route('/api/orders/<int:order_id>')

@login_required # confirms user is logged in

def get_order(order_id):

# VULNERABLE: fetches ANY order by ID, no ownership check

order = db.query('SELECT * FROM orders WHERE id = ?', order_id)

return jsonify(order)

# Attacker is logged in as user 1001.

# They request /api/orders/1002 and get user 1002's order.

# Then /api/orders/1003… /api/orders/1004…

# Sequential enumeration exposes every order in the database.

2. The Three IDOR Variants — Understand All of Them

Variant 1: Horizontal Privilege Escalation (User-to-User)

The most common variant. A user can access another user's data at the same privilege level. User A can see User B's orders, invoices, medical records, messages, or account details simply by changing an identifier in the request. No admin privilege is required. No special knowledge is needed beyond the structure of the identifier.

# Horizontal IDOR — user to user

# Normal request from user 1001:

GET /api/user/profile?user_id=1001

Authorization: Bearer <user_1001_token>

# IDOR attack — same token, different user_id:

GET /api/user/profile?user_id=1002

Authorization: Bearer <user_1001_token>

# If the server returns user 1002's profile data — IDOR confirmed.

# The fix: verify that current_user.id == user_id before returning data.

Variant 2: Vertical Privilege Escalation (User-to-Admin)

A lower-privileged user accesses resources or functionality intended for higher-privileged users. Changing a document_id parameter reveals an admin-only document. Accessing an endpoint with a different user_type parameter exposes admin functionality. This variant is rarer but more severe — it can expose administrative functions, system configurations, or all user data in the system.

Variant 3: Indirect IDOR (Non-Numeric Identifiers)

IDOR is not limited to sequential integer IDs. Many developers believe that if they use GUIDs (globally unique identifiers), hashed values, or non-sequential identifiers, they are protected. They are not. If the identifier is user-controlled and the application trusts it without authorisation checking, the vulnerability exists regardless of identifier format. Indirect IDOR occurs when the exposed identifier is less obvious — a filename, a hash, a username, an email address — but still directly references a protected object.

# Indirect IDOR — file download by filename

GET /download?file=user_1001_invoice_march.pdf

# Attacker guesses or enumerates:

GET /download?file=user_1002_invoice_march.pdf

GET /download?file=admin_config_backup.json

# GUID-based IDOR — still vulnerable without authorisation check

GET /api/documents/a1b2c3d4-e5f6–7890-abcd-ef1234567890

# If attacker finds this GUID (from another endpoint, email, JS source),

# the application still needs to verify ownership before serving the doc.

— — —

3. Real-World Impact — What IDOR Actually Exposes

#1

OWASP API Top 10

Broken Object Level Authorisation

$10K+

Avg bug bounty payout

Critical IDOR on major platforms

70%

API tests show BOLA

Salt Security API Threat Report

Millions

Records exposed

In notable IDOR breaches 2020–2025

IDOR vulnerabilities have been responsible for some of the most significant data exposures of the past decade. A major Indian healthcare platform exposed patient records through predictable UHID numbers in API endpoints. A fintech application exposed loan application documents by incrementing a document reference number. A ride-sharing platform exposed driver location history through a predictable trip ID. A social media application exposed private messages by iterating through message thread identifiers.

In each case, the data that was exposed was data that the affected users had every reasonable expectation of privacy over — medical records, financial documents, location history, private communications. The vulnerability was not sophisticated. It did not require advanced exploitation techniques. It required changing a number in a URL.

— — —

4. How to Find IDOR — A Penetration Tester's Methodology

Finding IDOR during a penetration test or bug bounty requires a systematic approach. Here is the methodology I use.

STEP 1: MAP ALL OBJECT REFERENCES

Intercept all application traffic using a proxy (Burp Suite is the standard). Identify every request that contains an identifier that references a server-side object: integers in URL paths, GUIDs in parameters, usernames in request bodies, file paths in download links, account numbers in headers. This mapping is the foundation of the IDOR assessment.

STEP 2: CREATE TWO ACCOUNTS

The most reliable IDOR testing approach uses two separate accounts at the same privilege level. Create User A and User B. Log in as User A and capture the identifiers of objects that belong to User A — orders, documents, messages, profile data. Then, logged in as User B, attempt to access User A's objects using those identifiers. Any successful access is an IDOR.

# Burp Suite Intruder — IDOR enumeration

# Target: GET /api/invoices/§1001§

# Payload: Sequential numbers 1000–2000

# Auth: User B's session token

# Filter results by:

# — Response code 200 (successful access)

# — Response length != 'not found' response length

# — Response body containing data (not empty)

# Each 200 response containing data from another user = IDOR confirmed

# FFUF equivalent for API IDOR testing:

# ffuf -u https://target.com/api/orders/FUZZ \

# -w numbers.txt \

# -H 'Authorization: Bearer <user_b_token>' \

# -mc 200 -fs <empty_response_size>

STEP 3: TEST ALL HTTP METHODS

IDOR is not limited to GET requests. Test POST, PUT, PATCH, and DELETE requests with modified object identifiers. A user might not be able to read another user's data (the GET is protected) but might be able to modify or delete it (the PUT or DELETE is not). Also test API endpoints that appear in JavaScript source files, mobile app decompiled code, or Swagger/OpenAPI documentation — these often expose endpoints that were not discoverable through the standard application flow.

STEP 4: TEST PARAMETER POLLUTION AND JSON BODIES

Object identifiers are not always in URL paths. They appear in JSON request bodies, in hidden form fields, in HTTP headers, and in cookies. A change of user_id in a POST body, or account_number in a JSON update request, may expose IDOR that URL-based testing would miss.

# IDOR in JSON request body — often missed in testing

POST /api/user/update-preferences

Authorization: Bearer <user_b_token>

Content-Type: application/json

{

"user_id": 1001, // changed from 1002 (user B's actual ID)

"notifications": true,

"theme": "dark"

}

# If server processes this and updates user 1001's preferences,

# this is an IDOR allowing user B to modify user A's data.

# Also test: adding user_id to requests that don't normally include it.

# Some poorly implemented endpoints will honour the parameter if present.

— — —

5. How to Fix IDOR — The Developer's Remediation Guide

IDOR is one of the most fixable vulnerabilities in web security. The root cause is always the same: a missing or insufficient authorisation check. The fix is always the same: add the check.

Fix 1: Always Verify Ownership at the Data Layer

# VULNERABLE: fetches any order by ID

def get_order(order_id):

order = db.query('SELECT * FROM orders WHERE id = ?', order_id)

return jsonify(order)

# SECURE: always include user ownership in the query

def get_order(order_id):

user_id = get_current_user_id() # from session/JWT

order = db.query(

'SELECT * FROM orders WHERE id = ? AND user_id = ?',

order_id, user_id

)

if not order:

return error(403, 'Forbidden') # or 404 to avoid enumeration

return jsonify(order)

# The WHERE user_id = ? clause is the fix.

# It is impossible to return another user's order even if the ID is known.

Fix 2: Use Indirect References Where Possible

Instead of exposing database primary keys in API responses, use indirect reference maps — session-scoped mappings between a temporary reference and the actual database ID. The user sees reference Ƈ' which maps, in their session only, to order ID 48291. Another user's session maps Ƈ' to their own first order. Cross-session enumeration becomes impossible because the references are not globally meaningful.

Fix 3: Implement Centralised Authorisation Middleware

Ad-hoc authorisation checks — where each endpoint developer remembers to add the ownership check — will eventually be missed. The robust solution is a centralised authorisation layer that intercepts requests before they reach endpoint handlers and enforces ownership policies based on the resource type and the authenticated user. Frameworks like Casbin, OPA (Open Policy Agent), or custom middleware can enforce this consistently across all endpoints.

Fix 4: Avoid Sequential Identifiers in Sensitive Contexts

While GUIDs alone do not fix IDOR (the authorisation check is still required), replacing sequential integer IDs with UUIDs or other non-guessable identifiers makes enumeration attacks significantly more difficult. Sequential IDs make it trivial to enumerate all objects in a system. Non-sequential IDs require the attacker to know the specific identifier — which does not eliminate IDOR but raises the bar for opportunistic enumeration.

Remediation

Fixes Enumeration

Fixes IDOR Root Cause

Implementation Complexity

Ownership check in query

No

Yes — this is the actual fix

Low — one WHERE clause

Non-sequential IDs (UUID)

Yes

No — still need auth check

Low — change ID generation

Indirect reference maps

Yes

Yes — removes global references

Medium — session management

Centralised authorisation middleware

N/A

Yes — enforces consistently

High — architectural change

Return 404 instead of 403

Partial

No

Low — masks enumeration

— — —

The Core Lesson

IDOR persists despite being well-understood, well-documented, and consistently top-ranked in vulnerability frameworks because it requires a cultural shift as much as a technical one. Every developer needs to internalise a single question: for every endpoint that retrieves, modifies, or deletes data — does this endpoint verify that the currently authenticated user is allowed to access this specific resource?

Not 'is the user authenticated?' That check is usually present. The question is 'does this user own or have permission to access this specific object?' That check is the one that is systematically missing in vulnerable applications, and it is the one that IDOR exploits every time.

"Authentication checks who you are. Authorisation checks what you are allowed to do. IDOR happens when the second check is absent. The fix is always: add the second check."