CYBER-02-002-P · Primer — A01 Broken Access Control
The idea in one sentence
Broken access control is when the server fails to enforce who is allowed to do what, so an ordinary user reaches data and actions that were never theirs.
Why it exists
Access control is the set of rules that says this user owns this order and only an admin may delete an account. Almost every application has these rules — the trouble is where they get enforced. Broken access control is simply the server failing to check the rule on a request it actually receives. OWASP ranked it the number one web application risk in the 2021 Top 10, after finding some form of it tested in 94% of the applications they reviewed. It is not an exotic bug; it is the most common one there is. The reason it is so common is misplaced trust: the developer assumes the request will look the way the UI produced it. But the UI is a suggestion, not a boundary — the browser is fully under the user's control, and the request is whatever they choose to send.
Think of it like a hotel front desk that only checks you have a reservation before handing you a keycard, but never checks which room the keycard actually opens. A guest could walk up, get a generic key, and then try the doors on the hallway until one opens, gaining access to a room that isn’t theirs. The front desk trusted the guest’s appearance instead of verifying the exact room assignment, just as a web server trusts a UI‑generated request instead of verifying the user’s rights.
How it works
It shows up in two shapes, and both are the same missing check.
Object-level (IDOR — Insecure Direct Object Reference, CWE-639). The app exposes a record by an identifier the user can see and change. You open your own invoice at /orders/1001. The server returns it — but it only confirmed that you are logged in, not that order 1001 belongs to you. Change the number in the address bar to 1002 and the page cheerfully loads a stranger's invoice. Automate the number upward and, minutes later, an attacker has swept the entire table. For example, a script that sends 1 request per second for IDs 1001‑2000 would retrieve 1,000 invoices in under 17 minutes, exposing every customer’s financial data.
Function-level (CWE-284, improper access control). The privileged action is reachable if you know its address. The "Admin" link is hidden from your menu, so the UI looks locked down — but /admin/delete-user still answers a request, because the server never checks your role. Hiding the button removed it from the screen, not from the API. This is "forced browsing": guessing or replaying a URL the interface didn't offer you. An attacker could send a single POST to /admin/delete-user?id=12345 and, if the server lacks a role check, delete an arbitrary user in a single step.
See it — the missing check, then the control inserted
flowchart TD
A["User requests<br/>/orders/1002"] --> B{"Server checks<br/>logged in?"}
B -->|"yes"| C["Returns the order<br/>(no owner check)"]
C --> D["Stranger's invoice<br/>leaks"]
B -. "control inserted" .-> E{"Does THIS user<br/>own 1002?"}
E -->|"no"| F["403 Denied<br/>+ log the attempt"]
E -->|"yes"| G["Return the order"]
The one gotcha
The fatal misconception is treating UI hiding as a security measure. If the server does not explicitly deny a request, it implicitly allows it. A greyed‑out button or a missing menu entry is only a visual cue; it does not stop a determined client from crafting the same HTTP call. The only reliable gate is a server‑side check that explicitly verifies the user’s permission for the specific object or operation, and the default stance must be deny.
The one-line takeaway
Enforce every access decision on the server, and deny by default.
Where you'll meet it
Every framework hands you the enforcement primitives — Django's object‑level permissions and PermissionRequiredMixin, Rails with Pundit or CanCanCan policies, Spring Security's @PreAuthorize and method security. Use them on the server, not just to render the page. You test for broken access control with an intercepting proxy — Burp Suite or OWASP ZAP — by changing IDs and replaying requests as a lower‑privileged user (a defensive activity, done against your own app). The same class shows up in the cloud: an over‑broad IAM policy, an S3 bucket that trusts the caller, or a workload that can read the instance metadata service (IMDS) it shouldn't. It maps to CWE-284 (improper access control) and CWE-639 (authorization bypass through a user‑controlled key), and it sits at number one on the OWASP Top 10 2021.
Named concepts: broken access control, IDOR, insecure direct object reference, function-level authorization, deny by default
