← Vulnerability class · WebApp Security

Broken Access Control

How an app that correctly identifies a user can still let them reach data or actions they were never authorized for.

Typical ceiling: CriticalA01:2021 – Broken Access ControlCWE-284CWE-639
Ask Synthreat AI about Broken Access Control ✦Practice this on PortSwigger's Web Security Academy →

Definition

Broken access control happens when an application correctly figures out who a user is (authentication works fine) but fails to correctly enforce what that specific user is allowed to do or see (authorization fails). The login worked. The problem is everything that was supposed to happen after it.

The Trust Boundary That Breaks

Most developers build a clear check for “is this request coming from someone who’s logged in.” Far fewer build an equally consistent check for “is this specific logged-in user allowed to touch this specific record.” Those are two different questions, and a codebase can answer the first one perfectly on every single endpoint while quietly skipping the second on just a handful of them.

The trust boundary that actually needs to hold is per-record, not per-session: a valid session proves identity, nothing more. The moment a developer treats “has a valid session” as equivalent to “is authorized for this exact resource,” that boundary is gone, and the only thing standing between an attacker and someone else’s data is whatever value happens to sit in a URL or request body.

Where It Actually Shows Up

Why It Keeps Happening

Authorization logic is easy to write once and easy to forget to repeat. A developer adds a proper ownership check on the first version of an endpoint, then a second, similar endpoint gets added later (often by a different person, under deadline pressure) that copies the general shape of the first but omits the ownership check, because it “obviously” needs the same protection and nobody circled back to confirm it actually has it. Authorization, unlike authentication, rarely lives in one central, reusable place; it has to be re-implemented correctly on every single endpoint that touches user-specific data, and consistency is genuinely hard to maintain at scale.

How to Find It

  1. Enumerate every endpoint that accepts an identifier (an ID, a UUID, a filename) referencing a specific record.
  2. Using a valid, authorized test account, note a record it legitimately owns, then swap in the identifier for a different record owned by a separate, unrelated test account.
  3. Confirm the response is properly rejected. A generic error page is not proof of rejection; check that no unauthorized data was actually returned in the response body, since some endpoints fail “gracefully” on the surface while still leaking data underneath.
  4. Repeat the same test for state-changing requests (edit, delete), not just reads: an endpoint that correctly blocks viewing another user’s record can still fail to block editing or deleting it.
  5. Test vertical escalation separately: using a low-privilege account, call admin-only endpoints directly rather than relying on the UI to hide them.

Impact by Scenario

Scenario Realistic impact
IDOR on a low-sensitivity, read-only resource Limited exposure of non-sensitive data; still a real finding, but bounded
IDOR on financial, health, or personal records, read access only Serious privacy and regulatory exposure, scaled by how many records are sequentially or predictably enumerable
IDOR allowing edits or deletes on another user’s records Data integrity failure on top of exposure; an attacker can alter or destroy what they were never authorized to touch
Vertical escalation reaching genuine administrative functionality Full compromise of the application’s own trust model; the attacker now operates with the application’s highest privilege level

Why a Business Should Care

Broken access control is consistently one of the most common findings across real-world assessments, and it is often the cheapest class of finding for an attacker to exploit: no exotic payload is required, just a predictable ID and a willingness to change it. For a client, the useful framing is that this class scales with data volume. A single missed ownership check on a customer-record endpoint is not a one-off leak; it is a template an attacker can run against every sequential ID in the system, which is exactly why a single broken endpoint of this kind has produced some of the largest mass data exposures on record. It is also a class where “our authentication is strong” gives a client zero reassurance, since the entire failure happens after authentication succeeds.

A Worked Example

(Generalized from real assessment patterns. Company and identifiers below are invented; no real client, system, or data is referenced.)

Harrow Ridge Billing runs a customer portal where authenticated users view their own invoices at GET /api/invoices/{id}. Logging in as a legitimate test account and viewing that account’s own invoice returns the expected record at, for example, ID 4471.

Incrementing the ID by one, to 4472, while still authenticated as the same test account, returns a complete invoice belonging to a different, unrelated customer: full billing address, itemized charges, and the last four digits of a payment method. The endpoint checks only that a valid session exists; it never checks whether the session’s owner actually owns invoice 4472.

Testing continues by sampling a handful of further sequential IDs, confirming the pattern holds consistently rather than being a one-off anomaly, and confirming the same flaw exists on the equivalent PDF-download endpoint. Testing stops there: enough evidence exists to prove the entire invoice range is enumerable, without actually harvesting real customer data beyond what was needed to demonstrate the finding.

sequenceDiagram
    participant Attacker as Authenticated User (Account A)
    participant Portal as Customer Portal API (/api/invoices/)
    participant DB as Billing Database

    Note over Attacker,Portal: Step 1: Legitimate query for owned resource
    Attacker->>Portal: GET /api/invoices/4471 (Session: User A)
    Portal->>DB: Query invoice 4471
    DB-->>Portal: Return invoice 4471 record
    Portal-->>Attacker: HTTP 200 OK (User A's Invoice)

    Note over Attacker,Portal: Step 2: Modifying object reference to Target B
    Attacker->>Portal: GET /api/invoices/4472 (Session: User A)
    Portal->>Portal: Verify Session Valid? (YES)
    Note right of Portal: Flaw: Server fails to verify if User A owns 4472
    Portal->>DB: Query invoice 4472
    DB-->>Portal: Return invoice 4472 record (Belongs to User B)
    Portal-->>Attacker: HTTP 200 OK (CRITICAL: Full PII & Financial Data Leaked)

Severity Calibration

This instance rates Critical: unauthenticated in the sense that matters most (any valid low-privilege account, not just a compromised admin account, can reach it), trivially exploitable by anyone who can count, and demonstrated against real financial and personal data across a range of sequential IDs rather than a single isolated record. A version of this same bug on a resource containing nothing sensitive, or requiring a non-sequential, non-guessable identifier that isn’t practically enumerable, would rate meaningfully lower; the identifier’s predictability and the data’s sensitivity are doing the real work in this rating, not the vulnerability class by itself.

Remediation

The real fix is enforcing an ownership or permission check on every request that references a specific record, implemented server-side, on every endpoint, every time, ideally through a shared, centrally-tested authorization layer rather than hand-rolled per endpoint. Using non-sequential, non-guessable identifiers (a UUID rather than an incrementing integer) is a reasonable defense-in-depth layer on top of that, but it is not a substitute for it: a UUID that leaks once through logs, referrer headers, or a different flaw is just as exploitable as a sequential ID if the ownership check itself is still missing.

The common bad fix is relying on obscurity: assuming that because an ID is a long random-looking string, or because a link is not directly advertised anywhere in the UI, nobody will find it. Anything reachable by a request from an authenticated session is reachable by an attacker with a valid account of their own, and obscurity alone has never been a real access control.

AI Technical Drill

Test Your Understanding

On-demand, Gemini-generated technical drill testing root causes, exploit chains, and verified defenses.

🤖

Generate On-Demand Technical Quiz

Synthreat AI analyzes this topic's attack surface and compiles custom multiple-choice questions with verified explanations.

Question Count: