Intermediate levelFree

IDOR and BAC — manual methodology to find broken access control

How to identify IDOR (Insecure Direct Object Reference) and BAC (Broken Access Control) manually: dual-account testing, parameter swap, role escalation, hidden parameters.

Gorka El BochiMay 11, 202614 min

Quick answer

IDOR and BAC are still #1 on OWASP API Security in 2026. The rule: automated scanners don't find them — they require business context (what does this endpoint do?) and two accounts to verify the access boundary. The methodology: two accounts (A and B), capture A's requests in Burp, swap the IDs/cookies for B's, observe the response. If A can view/edit/delete B's resources, it's IDOR. If a normal user can reach admin endpoints, it's vertical BAC. Typical bounties: €500-€10,000+ depending on impact.


Setup — two accounts in parallel

The beginner's mistake: testing IDOR with a single account. Without account B you have no baseline — you don't know whether the server rejects A's user or whether the ID simply doesn't exist.

Stack:

  • Burp Suite (Community is fine) with two browsers (Chrome profile A, Firefox profile B).
  • Or two profiles of the same browser + extensions like Auth Analyzer or Autorize.
  • Match-and-replace in Burp to swap cookies automatically.
css
Browser A (Chrome)  →  Burp Proxy  →  target.com
Browser B (Firefox) →  Burp Proxy  →  target.com

Capture a request from A, send it to Repeater, change only the auth cookie/header for B's (everything else the same). If the response is the same as A's → IDOR confirmed.

[!tip] Auth Analyzer extension It authorizes Burp to re-send each request with a different user's session and compare the response. The fastest way to audit 50+ endpoints without a script.


The 8 surfaces where IDOR appears

#SurfaceExample URLTest
1Numeric path parameter/api/users/1234512344, 12346, 0, -1
2UUID path parameter/api/orders/abc-def-ghiB's UUID obtained another way
3Query parameter/profile?userId=42Change to 43
4Body JSON field{"userId": 42, "data": "..."}Swap userId to B's
5Custom headerX-User-Id: 42Change to 43
6Cookie valueCookie: uid=42; ...Change to 43
7Hidden form field<input type="hidden" name="user_id" value="42">Change before submit
8JWT claim{"sub": "42", "role": "user"}Re-sign with sub=43 if the signature is weak

The golden trick: many apps validate the ID in the path but not the one in the body. If the endpoint is POST /api/users/42/profile with body {"userId": 42, "newName": "..."}, try leaving the path at 42 but putting userId: 43 in the body. Frequently the server ignores the path and trusts the body.


How to obtain other users' IDs

Without B's IDs (or other users') you can't confirm IDOR. Sources:

  • Your own account B: register it with a different email and observe which IDs appear.
  • Public listing endpoints: /users/search, /api/leaderboard, /comments?postId=X.
  • Features that leak IDs: avatars (/avatars/USER_ID.jpg), notifications (from_user_id), public posts.
  • Shared URLs: invitation, magic link, purchase receipt that includes an order ID.
  • Source maps + JS bundles: sometimes the frontend routers list hardcoded IDs from demos.
  • GraphQL introspection + search queries without a visibility filter.

[!warning] Privacy + scope If you find an IDOR that leaks PII (email, phone, address, national ID), document it WITH your own control account — do not run bulk extraction against real users. Most programs forbid extracting >1-2 records from non-owned accounts.


Horizontal vs vertical IDOR

Horizontal — same role, different identity. User A accesses user B's resources:

http
GET /api/orders/B_ORDER_ID
Authorization: Bearer JWT_DE_A
                                    ← 200 OK con datos de B = IDOR horizontal

Vertical — privilege escalation. A normal user accesses admin endpoints/functions:

http
DELETE /api/admin/users/12
Authorization: Bearer JWT_DE_USER_NORMAL
                                    ← 200 OK = BAC vertical

Vertical weighs more in CVSS — usually Critical (9.0+). Typical bounties €5K-€20K+.


Parameter swap — the most profitable pattern

Almost all profitable IDORs come from swapping a parameter in an endpoint that looks harmless:

Email change — without verification

http
PUT /api/users/me/email
{"email": "atacante@evil.tld", "userId": ID_DE_VICTIMA}

The server updates the email of the body's userId, ignores the me in the path → password reset via a controlled email → ATO.

Password change — oldPassword not verified

http
POST /api/users/CHANGE_PASSWORD
{"userId": ID_VICTIMA, "newPassword": "atacante"}

The endpoint doesn't ask for oldPassword. Typically a €3K-€8K bounty.

Order cancellation — refund to the attacker's account

http
POST /api/orders/B_ORDER_ID/refund
{"refundTo": "atacante_bank_account"}

Role assignment on a team

http
POST /api/teams/T_ID/members
{"userId": ATACANTE_ID, "role": "owner"}

If the endpoint doesn't verify that the actor is the team's owner → any user can join any team as owner.


Hidden parameters — the ones that generate big bounties

Many endpoints accept parameters that don't appear in legitimate UI requests. The backend processes them if they're present, but the frontend never sends them. Result: undocumented features, sometimes admin ones.

Wordlist of frequent parameters

kotlin
admin, isAdmin, is_admin, role, userRole, accountType
debug, test, dev, sandbox, preview
force, bypass, override, skipAuth
verified, isVerified, emailVerified
status, accountStatus, suspended, banned
internal, system, super, root
hidden, public, visible

Tools

bash
# Arjun — descubre parámetros HTTP ocultos
arjun -u https://target.com/api/users -m GET

# Param Miner (Burp extension)
# Right-click request → Extensions → Param Miner → Guess params

Examples of real bounties with hidden params

  • POST /api/users with a normal body {email, password} + extra {"role": "admin"} → new user created as admin.
  • PUT /api/posts/X with extra {"approvedBy": "system"} → bypasses the moderation queue.
  • GET /api/orders/X?debug=true → expanded response with internal fields (PII, internal IDs, cost).

Function-level BAC — endpoints the frontend never calls

The normal UI never calls /api/admin/users from a normal user — but the endpoint still exists and sometimes doesn't check the role.

How to find them

  1. Log in as admin (a test account if the program allows, or a normal user with high privileges).
  2. Capture all the endpoints the admin dashboard calls.
  3. Log out, log in as a normal user.
  4. Re-fire the same endpoints. Is there one that responds 200?

You frequently find endpoints that verify the token (auth) but not the role (authorization).

http
admin → GET /api/admin/users → 200 [lista de users]
user  → GET /api/admin/users → 200 [lista de users]    ← BAC vertical

Method swap

Some endpoints only validate the correct HTTP method. Try:

swift
POST /api/admin/users      403 Forbidden
GET  /api/admin/users      200 OK              BAC
PUT  /api/admin/users/42   403 Forbidden
PATCH /api/admin/users/42  200 OK              BAC

Trailing slash + extension trick

Some WAFs filter exact paths. Try variants:

swift
/api/admin/users      403
/api/admin/users/     200             trailing slash bypass
/api/admin/users.json  200            extension
/api/admin/users;.css  200            matrix params
/api/admin/users%20   200             trailing whitespace
/api/Admin/users      200             case
/api/admin//users    → 200            ← double slash

IDOR in GraphQL

GraphQL multiplies the surface: a single endpoint exposes N queries. Access control usually lives at the resolver level, not the field level — it's very easy to miss one.

Test pattern

graphql
query {
  user(id: "OTRO_USER_ID") {
    email
    phoneNumber
    addresses { street }
    paymentMethods { last4 }
  }
}

If the app doesn't check ownership in the user resolver, it returns it. Repeat with node(id: ...) (a common interface in relay) and with search queries without a visibility filter.

Mutations

graphql
mutation {
  updateUser(id: "VICTIMA_ID", input: { email: "atacante@evil.tld" }) {
    id
  }
}

Many GraphQL backends verify that the user is authenticated but not that they own the id they're asking to modify.


Reporting tips

  • Severity boost: if the IDOR leaks regulated PII (national ID, medical, financial data) → privacy impact, GDPR.
  • Account takeover chain: if IDOR allows changing the email → immediate ATO, severity Critical.
  • Bulk extraction warning: document the pattern with 2-3 records from your account + a control account. Do NOT extract thousands.
  • Bypass docs: list every filter the server attempted (path validation, body validation, method check) and how you bypassed it — it raises the reward and leads to a more solid dev fix.

Hunting checklist

  • Set up two accounts (A and B) in parallel with Burp/Auth Analyzer.
  • Map every endpoint that accepts an ID in path, query, body, header or cookie.
  • Swap the ID with B's session, observe the response.
  • Try all actions (GET / PUT / DELETE / POST) — some validate GET but not DELETE.
  • Hidden parameters with Arjun + Param Miner.
  • Log in as admin (if you can), capture admin endpoints, reproduce with a normal user.
  • Method swap + trailing slash + extension tricks on 403 endpoints.
  • GraphQL: try user(id), node(id), listing queries without a filter.
  • Cross-tenant: if the app is multi-tenant, try cross-org accesses.
  • Document with 2-3 records max, never bulk extract.

Practice horizontal/vertical IDOR, hidden params and function-level BAC in IDOR and BAC labs.

Practice this in a lab

Idor Api Newsletter Suscriptores

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account
Premium · 1 more technique

There's an extra payload at the end

Los 12 parámetros ocultos que tests automáticos no detectan pero generan bounties de €1500-€5000 con regularidad.

Unlock

€7.99/mo · cancel anytime

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy