Back

C1: Implement Access Control - Threats Addressed

Threats Addressed — What Goes Wrong Without It

⚠️ EDUCATIONAL PURPOSE ONLY — the techniques below are shown so you understand the threats the access-control control neutralizes, and can verify your own systems. This page describes what attackers do when the control is missing or weak; the How to Implement page shows how to shut each one down.

This is the inverse view of C1. Where the overview defines the control, this page catalogs the concrete failures that appear in its absence. Every item below is a symptom of one missing property: no deny-by-default, no server-side check, no record-level ownership check, or authority trusted from the client. Read each as "here is the hole the control fills."

The unifying theme is cheapness. Broken access control is exploited not with elaborate payloads but by changing a value and re-sending an ordinary request. An identifier becomes a different identifier; a hidden field becomes admin; an unlinked path is requested directly. That low cost is exactly why OWASP ranks the risk first — and why the control matters.

The Threats This Control Prevents

1. Broken Object-Level Authorization (IDOR / BOLA)

The canonical failure. A resource is served by an identifier taken from the request, and the server never checks that the caller owns it. Incrementing or swapping the identifier returns another user's data.

GET /api/invoices/1043 HTTP/1.1      # my own invoice -> 200 OK
Authorization: Bearer <valid-token-for-user-A>

GET /api/invoices/1044 HTTP/1.1      # someone ELSE's invoice
Authorization: Bearer <valid-token-for-user-A>
→ 200 OK  { "customer": "user-B", "total": 4820.00, ... }
# Scripted, this becomes a full export:
for id in $(seq 1 100000); do
  curl -s -H "Authorization: Bearer $TOKEN" https://api.target/api/invoices/$id
done

Missing property: a record-level ownership check (does subject A own invoice 1044?). The control: verify entitlement to the specific object on every reference.

2. Broken Function-Level Authorization (Privilege Escalation)

An administrative or sensitive function is reachable by any authenticated session because the function-level check was assumed to be handled by the UI hiding the link.

# A normal user simply calls the admin endpoint directly:
POST /api/admin/users/55/role HTTP/1.1
Authorization: Bearer <valid-token-for-a-STANDARD-user>
Content-Type: application/json

{"role":"admin"}
→ 200 OK      # no server-side role check -> standard user is now admin

Missing property: a deny-by-default, server-side function-level check. The control: gate every privileged function on the server, independent of the UI.

3. Vertical Privilege Escalation via Parameter / Metadata Tampering

The request carries its own authority — a client-supplied role, tier, or flag — and the server trusts it instead of deriving authority from server-side state.

POST /api/account/update HTTP/1.1
Content-Type: application/json

{ "email": "attacker@evil.com",
  "role": "admin",            # client-supplied authority, honored by the server
  "account_tier": "enterprise" }
→ 200 OK   # server persisted role=admin because it trusted the body
<!-- Hidden fields are not a control; the attacker edits them at will -->
<input type="hidden" name="role" value="user">   <!-- changed to "admin" -->

Missing property: authority derived only from trusted server-side state. The control: ignore client-supplied roles/permissions; look up the subject's real privileges server-side.

4. Horizontal Privilege Escalation (Cross-Tenant / Cross-User)

A user reaches another user's data at the same privilege level by supplying someone else's identifier. Role checks pass because the roles are identical — only ownership differs.

GET /api/users/me/messages?account_id=8842 HTTP/1.1   # not my account
Authorization: Bearer <valid-token-for-account-7311>
→ 200 OK   # server keyed off the client-supplied account_id, not the token

Missing property: scoping every query to the authenticated subject's own tenant/records. The control: derive the owning account from the session/token, never from a request parameter.

5. Forced Browsing to Unprotected Resources

Sensitive endpoints are "protected" only by not being linked. Attackers discover them through wordlists, JavaScript, sitemaps, and history, and reach them directly.

# Discovered via a content-discovery wordlist, never linked in the UI:
GET /admin/dashboard           → 200 OK   (should require admin)
GET /reports/2026/export.csv   → 200 OK   (bulk data, unauthenticated)
GET /api/internal/debug        → 200 OK   (staging endpoint in prod)
GET /user/1/settings           → 200 OK   (another user's settings)

Missing property: enforcement on the resource itself, regardless of how it was reached. The control: deny by default; authorize every resource independently of whether it is linked.

6. Insecure Direct Access to Static / Stored Files

Files are served from a predictable path with no per-request authorization, so any user who knows or guesses the path downloads any other user's upload.

GET /uploads/medical/patient-7311-scan.pdf HTTP/1.1
→ 200 OK   # web server streams the file with no ownership check

# Predictable naming makes enumeration trivial:
GET /uploads/medical/patient-7312-scan.pdf   → 200 OK  (not mine)

Missing property: authorization in front of file delivery. The control: serve protected files through an authorizing handler that checks ownership, never straight from a public directory.

7. Missing Authorization on State-Changing Methods

Read paths are checked but write/delete paths on the same resource are not — or only some HTTP methods are gated.

GET    /api/documents/900   → 403  (read is correctly denied to non-owners)
DELETE /api/documents/900   → 204  (delete is NOT checked -> data destroyed)
PUT    /api/documents/900   → 200  (overwrite is NOT checked -> tampering)

Missing property: the same ownership/role check on every verb and action. The control: authorize the action, not just the read; cover create/update/delete uniformly.

8. Trusting Unverified Token Claims

The application reads authority from a token or cookie without validating it server-side, so a forged or altered claim is honored.

# JWT payload edited; signature not verified, or "alg":"none" accepted:
{ "sub": "user-A", "role": "admin" }   # attacker changed role from "user"
→ server grants admin because it trusted the decoded claim

Missing property: cryptographic verification plus a server-side authority lookup. The control: verify token integrity and resolve real permissions from trusted state, not from unverified claims.

9. Enumeration Without Detection

Even where individual checks exist, the absence of logging lets an attacker probe thousands of object references undetected until they find the one gap.

# 40,000 requests, 39,997 denied, 3 succeed -- and nobody is alerted:
GET /api/orders/1  403
GET /api/orders/2  403
...                       # a wall of denials that should have triggered an alarm
GET /api/orders/7311  200 # the one missed check, now being harvested

Missing property: logging and alerting on access-control failures. The control: record every denial with subject/resource/action and alert on abnormal denial rates.

Threat Summary

#ThreatAttacker actionMissing property the control supplies
1IDOR / BOLASwap a record IDRecord-level ownership check
2Broken function-level authzCall an admin endpoint directlyServer-side function gate, deny by default
3Parameter/metadata tamperingSend role=adminAuthority from server state only
4Horizontal escalationSupply another account_idScope queries to the subject
5Forced browsingRequest an unlinked pathDeny by default on every resource
6Direct file accessGuess a file pathAuthorize file delivery
7Unchecked write methodsDELETE/PUT a non-owned recordAuthorize every action/verb
8Trusting token claimsEdit a JWT claimVerify + server-side lookup
9Undetected enumerationProbe IDs at scaleLog and alert on failures

How These Threats Chain

In real incidents these rarely appear alone. A single missing check becomes a full breach when combined:

Forced browsing finds /api/users/{id}      -> endpoint has no ownership check
        +
IDOR over the id parameter (1..N)          -> enumerate every user record
        +
No logging on repeated 200s from one IP    -> harvesting goes unnoticed
        =  full customer database exfiltrated with ordinary GET requests
Parameter tampering sets role=admin        -> server trusts client authority
        +
Function-level endpoint unprotected        -> /api/admin/* now reachable
        =  standard account escalates to full application takeover

Mapping Threats to the Control

Every threat above is defeated by one or more properties of C1. That is the point of implementing the control as a coherent whole rather than patching symptoms:

Key Takeaways

  1. These are symptoms of one absent control — not nine unrelated bugs. Implement C1 and the whole family closes.
  2. Exploitation is cheap — changing an ID or a field and re-sending is the entire technique.
  3. Horizontal beats vertical in the wild — most breaches are user-reaching-user, which role checks alone miss.
  4. The UI is never the control — every threat here bypasses the browser and calls the server directly.
  5. Silence is a vulnerability — without logging, enumeration succeeds by attrition.

Next Steps