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 (1)
Hi, fellow bug bounty hunter here. Still learning as we all are and this is such a great article. I am actually going to do this with a bug bounty program today or tomorrow, thoroughly check it. Or maybe even over several days. I love how you provided from basic understanding, to even writing up the report. This is so clear, and easy to understand. Thanks again! I'll be reading your other stuff too. :)