Access Control Testing (FIND/EXPLOIT — OWASP A01)
Overview
Verify that every object and every action enforces "is this caller allowed?" server-side. Broken access control is the most common and highest-impact real-world flaw — and the easiest to miss because the happy path works perfectly.
Core principle: Authentication proves who you are; authorization proves what you may do. Test the second one on every object and every action — not just the UI-visible ones.
Applies when / Skip when
- Applies when: the app has authentication, multiple users, roles, or multi-tenant data — any resource or action that should be restricted. (Almost every non-trivial app.)
- Skip when: the app is fully public/anonymous with no per-user or privileged data → N/A.
- If N/A: report "access-control-testing: N/A — no auth/multi-user surface" and stop. Don't invent access boundaries that don't exist.
⚠️ Authorization
Use test accounts you created on an app you own/are authorized to test. Two accounts (A and B) plus one admin/one low-priv account is the cleanest setup.
The test matrix (run every cell)
| Read | Write/Update | Delete | Admin action | |
|---|---|---|---|---|
| As another user (horizontal) | A reads B's object? | A edits B's? | A deletes B's? | — |
| As lower privilege (vertical) | user hits admin read? | user hits admin write? | — | user invokes admin fn? |
| Unauthenticated | no token → data? | no token → mutate? | — | no token → admin? |
If any cell succeeds, that's a finding.
Method
- Capture a legitimate request as user A (note every id, uuid, slug, filename).
- Swap the object reference to B's resource, keep A's session → IDOR/BOLA.
- Swap the session (remove token / use lower-priv token) on privileged endpoints → BFLA / vertical.
- Force-browse hidden routes (
/admin,/api/internal) with a normal account. - Mass assignment — add fields the UI never sends (
role,isAdmin,userId,price). - Check every method —
GETmay be guarded butPUT/DELETE/PATCHforgotten.
See references/access-control-tests.md for concrete request recipes and edge cases.
Quick reference — bug classes
| Class | Test |
|---|---|
| IDOR / BOLA | Change an id/uuid/filename to another user's; do you get their data? |
| Horizontal privesc | Act on another same-level user's resources. |
| Vertical privesc | Low-priv account reaches admin function/data. |
| BFLA | Admin endpoint guarded in UI but callable directly by anyone. |
| Multi-tenant break | Tenant A reads tenant B (swap org_id/tenant_id). |
| Mass assignment | Inject privileged fields into the request body. |
| Forced browsing | Guess/enumerate unlinked privileged routes. |
Output
Findings in the register: which account hit which endpoint/object, the exact swapped request, what was exposed, and horizontal vs vertical. Severity is almost always High+.
Hand-off
Confirmed on a running app → reproduce via active-pentest; chain with others in
vulnerability-chaining; fix via security-hardening (deny-by-default, server-side checks).
Common mistakes
- Testing only as admin — you'll never see the bug. Always test as the lower privilege.
- Trusting that a hidden/unlinked endpoint is safe — enumerate and call it directly.
- Checking
GETonly — mutations are where damage happens. - Assuming UUIDs are safe — they leak (in responses, logs, referrers) and are still IDOR-able.
- Forgetting predictable patterns: sequential ids, base64'd ids, guessable filenames.