Frontend Labs
⋯

Chapter 45 — Broken access control

In one sentence

Access control is enforcing who may do what to which object — and it's OWASP's #1 category because it must be checked on every request to every object, server-side, and one missed check is a breach.

The failure modes

   IDOR / BOLA (Broken Object-Level Authorization)
     GET /api/orders/1001   -> your order
     GET /api/orders/1002   -> someone else's order, returned happily
     The app checks you're LOGGED IN but not that the object is YOURS.
     #1 API vulnerability (OWASP API Top 10 A01). Also with UUIDs if they
     leak; obscurity is not authorization.

   BROKEN FUNCTION-LEVEL AUTHORIZATION (BFLA)
     POST /api/admin/users/5/promote  -> works for a normal user because
     the endpoint only checks authentication, or hides the button but not
     the route ("security by missing link").

   VERTICAL escalation: user -> admin.
   HORIZONTAL escalation: user A -> user B's data.

   MASS ASSIGNMENT / auto-binding
     PATCH /api/users/me  {"name":"x","role":"admin","credits":99999}
     The app binds the whole JSON to the model; you set fields you
     shouldn't. Fix: explicit allowlist of updatable fields (DTOs).

   PATH TRAVERSAL
     GET /download?file=../../../../etc/passwd
     GET /download?file=..%2f..%2f (encoded)  / ....// (filter bypass)
     Fix: resolve to a canonical absolute path, assert it's inside the
     intended base dir; better, use an opaque ID -> lookup, never a path.

   MISSING RE-CHECK ON SECOND STEP
     Multi-step flows (checkout, wizards) that authorize step 1 and trust
     hidden fields / prior state on step 3. Re-derive and re-check server-
     side at every step.

   METADATA / SIDE-CHANNEL access
     Referer leaks, predictable exports, GraphQL introspection,
     debug endpoints, .git/ , /actuator, backup files, verbose errors.

The principles that prevent it

   1. DENY BY DEFAULT. Every route/handler requires an explicit allow.
      A new endpoint with no annotation must be UNREACHABLE, not public.
   2. ENFORCE SERVER-SIDE, on every request. The UI hiding a button is
      not access control. The client is attacker-controlled.
   3. CHECK THE OBJECT, not just the verb. "Can this user read THIS
      order?" -- scope every query by the owner:
        SELECT * FROM orders WHERE id = ? AND owner_id = :current_user
      (make ownership a WHERE clause, not an afterthought).
   4. CENTRALISE the decision. One authorization layer/middleware/policy
      engine (OPA, Cedar, Oso, your framework's guards), not ad-hoc `if`
      checks scattered per controller.
   5. USE OPAQUE, UNGUESSABLE IDs where you can (UUIDv4), but treat that
      as defence in depth -- still check ownership.
   6. LEAST PRIVILEGE everywhere: roles, scopes, DB grants, cloud IAM,
      service accounts. Time-box elevated access.
   7. LOG access-control decisions, especially denials, and alert on
      spikes (enumeration).
   8. TEST IT: for every endpoint, automated tests for "as another user",
      "as anonymous", "as lower role".

The SecureShop callback

Chapter 4 asked you to record your instinct for "per-customer authz" and "price-tampering". Here's the concrete version:

   GET /api/orders/:id
     BAD:   if (!session.user) return 401; return db.order(id)
     GOOD:  if (!session.user) return 401;
            order = db.query("SELECT * FROM orders WHERE id=? AND customer_id=?",
                             [id, session.user.id])
            if (!order) return 404      // 404, not 403 -> don't confirm existence

   POST /api/checkout
     BAD:   total = req.body.total          // client sends the price!
     GOOD:  recompute total server-side from cart items x current DB prices;
            ignore any client-supplied price/total/discount entirely.
            Validate the coupon server-side. (This is A01 + insecure design.)

Practice (40 min) — lab only

   1. IDOR: in Juice Shop, view your basket, then change the basket ID in
      the request to another value. Retrieve someone else's basket.
      Then find the "view another user's order" variant.
   2. BFLA: find an admin-only API call (watch the admin UI in devtools).
      Call it directly as a normal user.
   3. Mass assignment: register or update a profile and add "role":"admin"
      (or "isAdmin":true) to the JSON body. Check if it sticks.
   4. Path traversal: find a file-serving endpoint; try ../ and encoded
      variants to escape the intended directory.
   5. FIX locally: build a 3-endpoint app (list my orders, get order by id,
      update profile). Introduce each bug, exploit it, then fix with:
      ownership in the WHERE clause; a role guard middleware; a field
      allowlist DTO; canonical-path containment check. Add tests that call
      each endpoint "as user B" and expect 404/403.

Common confusions

  • "We use UUIDs, so no IDOR." Unguessable != authorized. UUIDs leak (URLs, Referer, logs, other endpoints). Still check ownership.
  • "The frontend only shows admins the button." The API is the boundary. Attackers call it directly.
  • "Role check at the top of the controller is enough." Object-level checks are separate: right role, wrong object. Scope every data access by owner/tenant.
  • "Return 403 for someone else's object." That confirms the object exists (enumeration). Prefer 404 for "not yours".
  • "We'll add authz tests later." Access control is the #1 breach cause and the easiest to regress silently. Test from day one.

Check yourself

  1. What is IDOR/BOLA, and why do UUIDs not fully fix it?
  2. Difference between vertical and horizontal privilege escalation.
  3. What is mass assignment and the fix?
  4. Why enforce access control server-side on every request, and why centralise it?
  5. In GET /api/orders/:id, show the query that makes ownership part of the check. Why return 404 rather than 403?
  6. Why must POST /checkout never trust a client-supplied total?

Further reading

  • Cheat sheets: OWASP "Authorization", "Access Control", "Insecure Direct Object Reference Prevention", "Mass Assignment".
  • List: OWASP API Security Top 10 — API1:BOLA and API5:BFLA are this chapter; the whole list is essential for anyone building APIs.
  • Course: PortSwigger Academy — "Access control vulnerabilities" track.
  • Tools: Authz policy engines — OPA/Rego, AWS Cedar, Oso, Casbin — read one's intro to see what "centralised" looks like.
  • Talk: "The Bug Hunter's Methodology" (Jason Haddix) sections on access control and IDOR hunting.

Generated from real-life-guide.md · 8 Oct 2026 16:36