How to Find IDOR Vulnerabilities: The Bug Bounty Hunter's Practical Guide
Insecure Direct Object References (IDOR) are consistently one of the highest-paid vulnerability classes in bug bounty programs. They're conceptually simple, devastatingly impactful, and — if you know where to look — surprisingly common even in mature applications.
This is the guide I wish I'd had when I started.
What Is IDOR, Actually?
IDOR happens when an application uses user-controllable input to access objects directly — without verifying the user has permission to access that specific object.
The classic example:
GET /api/users/12345/orders
Authorization: Bearer your_token_here
What happens if you change 12345 to 12346? If the server returns another user's orders — that's IDOR.
But modern IDOR is more nuanced than just incrementing numbers. Let's go deeper.
The IDOR Attack Surface Map
Before you start testing, build a mental map of where objects live in the application:
1. URL Path Parameters
/api/invoices/98432
/user/profile/johndoe
/documents/view/a1b2c3d4
2. Query Parameters
/api/export?userId=5001
/dashboard?account=ACCT-112233
/download?fileId=xyz789
3. Request Body
{
"recipientId": "user_abc123",
"amount": 50.00
}
4. HTTP Headers (less common, often overlooked)
X-User-ID: 5001
X-Account: ACCT-112233
5. Encoded/Hashed IDs
/api/orders/MTIzNDU2 (base64: "123456")
/profile/5d41402abc4b (MD5 hash)
Step-by-Step: Testing for IDOR
Step 1: Create Two Test Accounts
This is non-negotiable. You need Account A and Account B on the same application.
- Account A: The attacker (you're authenticated as this)
- Account B: The victim (owns the data you're trying to access)
Register both. Most programs let you self-register. Check the rules — some programs require using your @intigriti.me email.
Step 2: Map All Object Identifiers
While logged in as Account B, perform every action:
- Place an order
- Upload a file
- Save an address
- Create a comment or post
- Start a cart
Write down every object ID that appears in requests. Use Burp Suite's logger or browser DevTools → Network tab. These are your IDOR targets.
Step 3: Switch to Account A
Log out of B, log in as A. Now replay every request from Step 2 with B's object IDs.
Check:
- Can you read B's data? (Read IDOR)
- Can you modify B's data? (Write IDOR)
- Can you delete B's data? (Delete IDOR)
- Can you take an action as B? (Function-level IDOR)
Step 4: Test Without Authentication
Don't forget this — it's often missed:
curl https://api.example.com/orders/98432
# No Authorization header at all
If an unauthenticated request returns data, that's a critical vulnerability.
The Patterns That Pay
Pattern 1: Numeric Sequential IDs
The simplest case. If you see /api/orders/1001, try 1000, 999, 1002.
Pro tip: Don't just go +1/-1. Try:
- Your own IDs (to see the response format)
- IDs from public data (press releases, public profiles)
- Small integers (admin accounts are often ID 1, 2, 3)
Pattern 2: UUIDs — Not as Safe as You Think
UUIDs look unguessable: a7f3b2c1-4d5e-6f78-9012-3456789abcde
But IDOR with UUIDs still exists when:
- UUIDs appear in public URLs (shared links, emails)
- UUIDs are exposed in API responses you already have access to
- The same UUID is reused across resources
If you can get Account B's UUID from a shared link or public endpoint, you can test IDOR with it.
Pattern 3: Hashed or Encoded IDs
/profile/5d41402abc4b → MD5("hello")
/file/MTIzNDU2 → base64("123456")
Decode these. If they map to sequential integers, test them.
Tools:
- CyberChef for encoding/decoding
-
echo -n "MTIzNDU2" | base64 -din your terminal
Pattern 4: Function-Level IDOR
Sometimes the ID isn't the issue — the function is.
POST /api/admin/users/delete
{"userId": "12345"}
Can a regular user call admin functions if they know the endpoint? This is function-level IDOR (sometimes called Broken Function Level Authorization).
Pattern 5: Mass Assignment / Property Injection
// Normal request:
{"name": "Updated Name"}
// IDOR attempt:
{"name": "Updated Name", "role": "admin", "accountId": "other_account"}
Try adding extra properties to see if the server assigns them without checking permissions.
Where to Look in APIs
Modern apps are API-first. Here's where IDOR hides:
REST APIs
Look for resource endpoints that return or modify user data:
GET /api/v1/users/{id}
PUT /api/v1/users/{id}
DELETE /api/v1/users/{id}
GET /api/v1/orders/{id}
GET /api/v1/documents/{id}/download
POST /api/v1/messages/{id}/reply
GraphQL
GraphQL IDOR is underexplored territory. Try querying other users' data:
query {
user(id: "other_user_id") {
email
orders {
total
items
}
}
}
Also try introspection if it's enabled — it reveals the full schema.
WebSocket Messages
Real-time apps use WebSockets. Intercept the messages and look for IDs:
{"action": "subscribe", "channelId": "user_5001"}
Can you subscribe to another user's channel?
Automating IDOR Discovery
Manual testing works for specific endpoints, but automation finds what you'd miss.
Burp Suite Intruder
- Find a request with an object ID
- Send to Intruder
- Mark the ID as the payload position
- Load a numeric range (1–10000) or a list of known IDs
- Filter responses by status code and response length
ffuf for Parameter Fuzzing
ffuf -u "https://api.example.com/api/orders/FUZZ" \
-w id_list.txt \
-H "Authorization: Bearer your_token" \
-fc 403,404 \
-o results.json
Custom Script for API IDOR
import requests
BASE_URL = "https://api.example.com"
TOKEN_A = "your_token_here" # Account A's token
KNOWN_ID = "account_b_order_id" # Found from Account B's session
headers = {"Authorization": f"Bearer {TOKEN_A}"}
# Test read access
r = requests.get(f"{BASE_URL}/api/orders/{KNOWN_ID}", headers=headers)
if r.status_code == 200:
print(f"IDOR FOUND: {r.json()}")
elif r.status_code == 403:
print("Access denied — properly protected")
else:
print(f"Unexpected: {r.status_code}")
Impact Assessment: What Makes IDOR High Severity?
Not all IDOR is equal. Here's how programs evaluate severity:
| Scenario | Typical Severity | Why |
|---|---|---|
| Read another user's private messages | High | PII exposure at scale |
| Access another user's payment methods | Critical | Financial data |
| Read order history | Medium-High | PII + order details |
| Modify another user's profile | High | Account takeover potential |
| Delete another user's data | High | Data integrity |
| Access admin user data | Critical | Privilege escalation |
| Read your own old data | Informational | Not IDOR |
Multiplier factors:
- Scale: Can you access ALL users, or just specific ones?
- Automation: Can you enumerate IDs to mass-scrape data?
- Authentication bypass: Is this IDOR exploitable without any account?
Writing a Good IDOR Report
A strong IDOR report answers three questions immediately:
- What can I access? (The object type and its sensitivity)
- How do I access it? (Exact reproduction steps)
- What's the impact? (Why does this matter at scale?)
Template:
Title: IDOR in /api/orders/{id} allows reading other users' full order history
Summary:
An authenticated user can access any other user's order history by substituting
their own order ID with another user's ID in the /api/orders/{id} endpoint.
No additional authorization check is performed.
Steps to Reproduce:
1. Register Account A (attacker): attacker@example.com
2. Register Account B (victim): victim@example.com
3. As Account B, place an order. Note the order ID: ORDER-99876
4. Log in as Account A
5. Send request:
GET /api/orders/ORDER-99876
Authorization: Bearer [Account A's token]
6. Response: 200 OK with Account B's full order details including name,
address, items, and payment method last 4 digits
Impact:
Any authenticated user can read the complete order history of any other user.
With sequential order IDs, this enables mass data harvesting of all user orders.
Exposed PII includes: full name, shipping address, email, order contents.
Common Mistakes That Kill IDOR Reports
- Testing without two accounts — You can't prove IDOR if you only test your own data
- Not checking the HTTP method — GET might be protected but PUT/DELETE might not be
- Stopping at 403 — Some 403s are client-side, retry without certain headers
- Missing the impact statement — "I accessed data" isn't enough; explain the real-world risk
- Testing in production aggressively — Read the rules; some programs prohibit automated scanning
Practice Targets
Before hitting real programs, practice on legal targets:
- DVWA (Damn Vulnerable Web App) — local setup
- HackTheBox — machines with real IDOR challenges
- PortSwigger Web Academy — has IDOR labs with guided learning
- Pentesterlab — IDOR-specific badges and exercises
The IDOR Mindset
The best IDOR hunters think like this:
"Every piece of data returned by this API — who else's data could I request here?"
Apply this question to every endpoint. Every parameter. Every ID.
The difference between finding IDOR and missing it is usually just one more test.
Found this useful? I write about web security and bug bounty hunting. Follow for more practical guides.
Top comments (0)