August 14, 2026
[IDOR] The Ones Everyone Walks Past — Turning id=124 Into Account Takeover
What’s up everyone! Nitin here

By Nitin yadav
3 min read
IDOR is the bug people think is "too easy to still exist," so they skip it. Meanwhile it's consistently one of the top-paid, most-common findings on every platform, year after year — because devs keep bolting authentication onto the front door and forgetting to check authorization on every object behind it. The catch: the easy ?id=123 → ?id=124 ones are mostly patched. The ones that pay in 2026 are hiding — in GraphQL node IDs, nested JSON, second-order flows, and "harmless" GUIDs. Let's go find those.
The definition that actually helps
IDOR = the app checks who you are (authentication) but not whether this specific object is yours (authorization).
You're logged in as User A. You reference an object belonging to User B. The server hands it over because it only verified you're someone, not the right someone. Everything below is just: how do I find object references, and how do I swap them?
Step 1: Inventory every object reference
Proxy your whole session through Burp and hunt for identifiers in: URL path (/api/users/1042), query string (?user_id=), request body ({"authorId": 1042}), headers/cookies (X-User-Id), GraphQL variables, and export/download filenames (invoice_1042.pdf).
Create two accounts — non-negotiable. Account A (attacker) and Account B (victim). You need B's real object IDs to prove cross-user access cleanly without touching other users' data. Log every ID for both.
Step 2: The classic — still worth 5 seconds
GET /api/orders/1042 (yours)
GET /api/orders/1041 (someone else's?)GET /api/orders/1042 (yours)
GET /api/orders/1041 (someone else's?)Sequential numeric IDs still hit on newer/smaller apps, internal tools, and freshly-shipped features that skipped the check. Always try it first — but don't stop here, because the money moved deeper.
Step 3: GraphQL node IDs — where the real ones hide now
GraphQL loves base64 "global IDs" that look opaque and secure:
{ "variables": { "id": "VXNlcjoxMDQy" } }{ "variables": { "id": "VXNlcjoxMDQy" } }Decode it:
echo "VXNlcjoxMDQy" | base64 -d
# User:1042echo "VXNlcjoxMDQy" | base64 -d
# User:1042It's just Type:ID. Devs assume the base64 wrapper is a security boundary — it's an encoding, not a lock. Re-encode a different ID:
echo -n "User:1041" | base64
# VXNlcjoxMDQxecho -n "User:1041" | base64
# VXNlcjoxMDQxDrop it back in. Full PII disclosure often sits behind exactly this "it's base64 so it's fine" assumption.
Step 4: Nested JSON & mass-assignment IDORs
The IDOR is often inside the object, not in the URL:
PATCH /api/account
{ "userId": 1042, "email": "attacker@evil.com" } // swap userId to victim'sPATCH /api/account
{ "userId": 1042, "email": "attacker@evil.com" } // swap userId to victim'sChange userId/authorId and you might edit another user's data — that's account takeover, not just "info disclosure." Also try adding ID fields the UI never sends; servers sometimes honor a userId you inject (mass-assignment-flavored IDOR).
Step 5: GUIDs aren't safe, second-order, and method tricks
GUIDs: unguessable ≠ authorized. If you can leak another user's GUID anywhere (public profile, API response, shared link, error), reuse it — still IDOR. Randomness only stops blind enumeration.
Second-order: the ID you swap in request 1 only bites in request 2 (e.g. create a report about targetUserId:1041, then download it). Ask: did the app re-check ownership on the second call, or trust the first? Usually it trusts the first.
Method/routing tricks: GET /api/users/1041 → 403 but POST/PUT/PATCH/DELETE → 200. Add X-HTTP-Method-Override: GET. Try trailing /, .json, %00, ? — routing quirks sometimes skip the authz middleware.
Step 6: Automate the swap, verify by hand
Burp's Autorize (or AuthMatrix) is the workhorse: log in as User B, configure Autorize with User A's cookies, browse as B, and it replays every request as A and flags where A gets B's data. You review the ~12 flagged instead of clicking 500 by hand. For confirmed numeric IDOR, sample a few IDs to prove scale — never dump the whole user base.
The impact ladder (escalate for the bounty)
- Read another user's PII/messages/docs → info disclosure
- Modify their data (email, reset target, role) → account takeover
- Delete their resources → integrity/DoS
- Act as them (post, pay, transfer) → business-critical
Always push from "I can read record 1041" toward "I can take over account 1041 by changing its recovery email" — same root cause, 5–10× the payout.
Conclusion — the IDOR playbook
- IDOR = authenticated but not authorized on a specific object.
- Inventory every ID — path, query, body, headers, GraphQL, filenames.
- Decode base64 GraphQL node IDs and re-encode a different one.
- Check nested JSON fields — that's where ATO hides.
- GUIDs aren't safe if you can leak them; hunt second-order and method bypasses.
- Automate with Autorize, confirm with two accounts, escalate read → modify → takeover.