securityowaspnonfunctionalaccess-controlqa

Broken Access Control: How QA Tests Authorization (OWASP's #1 Risk)

The most common way to see someone else’s data isn’t a clever hack — it’s changing one digit in a URL. You open your own order at /orders/1043, change it to /orders/1044, and see a stranger’s order with their name, address and phone. This isn’t hypothetical: broken access control sits at #1 in the OWASP Top 10, and the good news for us is that QA catches most of these holes with plain hands — no pentester, no special tools. You just need to know where to poke.

Access control answers one question: “is this user allowed to perform this action on this object?” It breaks wherever the check was either forgotten or done only on the client. Let’s go by type.

IDOR — swap in someone else’s identifier

IDOR (Insecure Direct Object Reference) is when an object is fetched by an identifier from the request and the server doesn’t check whether it’s your object. The classic: an id in the URL, request body, a hidden form field, or a cookie.

How to test: create two users (A and B). As A, open your own resource — an order, document, profile, file — and note its id. Now, in B’s session (or with no auth at all), request that same id. You should get 403/404, not the other user’s data. Don’t test only numeric ids — GUIDs and hashes aren’t protection either if they can be discovered (in an email, in logs, in a shared link).

Where to look: file downloads (/download?file=...), viewing an order/booking/ticket, APIs like /api/users/{id}, exports, invoices, avatars, attachments.

Horizontal vs. vertical escalation

Two different things — test both:

  • Horizontal — access to a same-level user’s data. You’re a regular user reaching into another regular user’s data (essentially IDOR). “I can see someone else’s profile/order.”
  • Vertical — a regular user performs a higher-level action. “I’m a normal user, but I hit an admin endpoint and change someone’s role / delete a user / open the admin panel.” This is more dangerous.

Vertical test: log in as a normal user, find an admin action (from docs, from admin traffic, by guessing) and repeat it with your session. Permissions must be enforced on the server, not by “the button isn’t in the menu.”

Forced browsing — no button, but the handle still turns

The absence of a link in the interface is not protection. Direct navigation is a baseline test:

  • Open /admin, /settings/users, /reports directly as a normal user or guest.
  • Hit an API endpoint the UI shows only to admins.
  • Jump to a “success page” or a wizard step, skipping the previous steps.

If the content is served, access is broken — even if “you can’t get there in the interface.”

Test on the backend, not in the UI

The main QA trap: verify that the “Delete” button is hidden from a normal user and call it done. Hiding an element ≠ closing access. UI restrictions are convenience, not security. The real check is to fire the request itself:

  • Replay the action directly (DevTools → Network → “Copy as fetch/cURL”, Postman, Proxyman/Charles).
  • Remove or change the parameter that’s “supposed” to restrict (role=userrole=admin, isAdmin=falsetrue).
  • Send a request that doesn’t exist in your UI at all but exists for a higher role.

If the server executed it — that’s a hole, regardless of what the interface showed.

Tokens, cookies and roles

  • Another/old token: swap in another user’s token — access must be denied. An expired or revoked token must not work (check logout and password change — old sessions must be invalidated).
  • Role change on the fly: if a role is downgraded/banned, the active session must stop allowing access — not “after re-login.”
  • Tampering the token: if it’s a JWT, verify the server validates the signature and doesn’t trust a role field from the payload; switching alg to none must not work.
  • Tenant boundaries (multi-tenant): in B2B, verify a user from one company can’t see another’s data by swapping in a foreign tenant_id/org_id.

A role matrix — the base for your tests

Build a simple table: rows are actions/resources, columns are roles (guest, user, other user, manager, admin). Each cell is “allowed / not allowed.” Turn every “not allowed” into a negative test: make the request as that role and expect 403. This matrix covers horizontal, vertical, and forced browsing at once — and keeps you from forgetting a role.

Checklist

  • At least two users (A and B) plus a guest, for cross-checks.
  • IDOR: substituting another id (URL, body, hidden fields, cookies) → 403/404.
  • GUID/hash ids checked too (not protection if they leak).
  • Horizontal: access to a same-level user’s data is denied.
  • Vertical: a normal user can’t perform admin actions via the API.
  • Forced browsing: direct hits on /admin and hidden endpoints → denied.
  • Backend check: action replayed via request, not just “button hidden.”
  • Tampering role/flag params (role, isAdmin) grants no rights.
  • Foreign/expired/revoked token doesn’t work; logout invalidates the session.
  • JWT: signature validated, alg:none rejected, payload role not trusted.
  • Multi-tenant: isolation between companies/organizations.
  • A “role × action” matrix exists, each “not allowed” covered by a negative test.

The usual places it breaks

  • Endpoints added after the core feature (“we quickly bolted on export”).
  • Bulk operations and batch APIs (the check is placed on one object, forgotten on the list).
  • Direct links to files and attachments that bypass the app.
  • “Utility” and debug endpoints left in production.
  • GraphQL/aggregating queries where authorization exists on the top field but not the nested one.

In short — what to take with you

  • Broken access control is #1 in OWASP, and QA catches it by hand, no pentester.
  • The core test is to replay the request as a different role/user and expect 403 — don’t look at the UI.
  • IDOR, horizontal, vertical, forced browsing — four mandatory classes of checks.
  • A hidden button is not protection; the truth is on the backend.
  • A “role × action” matrix turns chaos into a set of negative tests.

Further reading: OWASP — A01 Broken Access Control · OWASP Web Security Testing Guide · OWASP — IDOR Prevention Cheat Sheet · PortSwigger — Access control vulnerabilities