DEV Community

Kai Learner
Kai Learner

Posted on

How to Find IDOR Vulnerabilities: The Bug Bounty Hunter's Practical Guide

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

2. Query Parameters

/api/export?userId=5001
/dashboard?account=ACCT-112233
/download?fileId=xyz789
Enter fullscreen mode Exit fullscreen mode

3. Request Body

{
  "recipientId": "user_abc123",
  "amount": 50.00
}
Enter fullscreen mode Exit fullscreen mode

4. HTTP Headers (less common, often overlooked)

X-User-ID: 5001
X-Account: ACCT-112233
Enter fullscreen mode Exit fullscreen mode

5. Encoded/Hashed IDs

/api/orders/MTIzNDU2  (base64: "123456")
/profile/5d41402abc4b  (MD5 hash)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

Decode these. If they map to sequential integers, test them.

Tools:

  • CyberChef for encoding/decoding
  • echo -n "MTIzNDU2" | base64 -d in your terminal

Pattern 4: Function-Level IDOR

Sometimes the ID isn't the issue — the function is.

POST /api/admin/users/delete
{"userId": "12345"}
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

GraphQL

GraphQL IDOR is underexplored territory. Try querying other users' data:

query {
  user(id: "other_user_id") {
    email
    orders {
      total
      items
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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

  1. Find a request with an object ID
  2. Send to Intruder
  3. Mark the ID as the payload position
  4. Load a numeric range (1–10000) or a list of known IDs
  5. 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
Enter fullscreen mode Exit fullscreen mode

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}")
Enter fullscreen mode Exit fullscreen mode

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:

  1. What can I access? (The object type and its sensitivity)
  2. How do I access it? (Exact reproduction steps)
  3. 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.
Enter fullscreen mode Exit fullscreen mode

Common Mistakes That Kill IDOR Reports

  1. Testing without two accounts — You can't prove IDOR if you only test your own data
  2. Not checking the HTTP method — GET might be protected but PUT/DELETE might not be
  3. Stopping at 403 — Some 403s are client-side, retry without certain headers
  4. Missing the impact statement — "I accessed data" isn't enough; explain the real-world risk
  5. 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)