Broken Access Control
debt(d7/e7/b7/t7)
Closest to 'only careful code review or runtime testing' (d7). The term's detection_hints list semgrep as the sole tool, but the metadata explicitly states automated=no and describes the pattern as a missing check — a static tool can flag routes lacking auth middleware but cannot reason about ownership semantics or IDOR logic. Most real instances surface only through careful code review or security testing, not automated scanning alone.
Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix says 'check authorisation on every request server-side', and common_mistakes reveal the problem appears in middleware, API routes, admin controllers, and UI logic simultaneously. Fixing it properly requires auditing and patching every route and controller action — a cross-cutting change that cannot be isolated to one file or component.
Closest to 'strong gravitational pull' (b7). The term applies_to web and api contexts broadly (not scoped to one component), and every new route, controller, or API endpoint added in the future must consciously implement and verify authorisation. The missing pattern exerts gravitational pull on all new feature development — every developer must remember to apply ownership checks on every resource operation, making it a persistent, wide-reaching structural concern.
Closest to 'serious trap' (t7). The misconception field explicitly identifies that developers believe hiding UI elements (obscurity) constitutes access control. Additionally, common_mistakes show developers conflate authentication with authorisation, and assume sequential IDs are private. These are well-documented contradictions of intuition — the 'obvious' UI-hiding approach is always insufficient — but not quite catastrophic since the correct mental model (server-side checks) is broadly taught, placing it at t7 rather than t9.
Also Known As
TL;DR
Explanation
Broken access control covers: horizontal escalation (accessing another user's data by changing an ID), vertical escalation (accessing admin functions as a regular user), missing function-level checks (assuming UI hiding is sufficient), insecure direct object references (IDOR), CORS misconfigurations, and JWT tampering. In PHP applications, enforce access checks server-side on every request — never rely on hidden form fields, client-side role checks, or obscure URLs. Centralise authorisation logic in a dedicated gate or policy class rather than sprinkling ad-hoc checks throughout controllers.
How It's Exploited
Diagram
flowchart TD
USER[Logged-in User<br/>Role: viewer] -->|GET /api/users/42| APP[Application]
APP -->|Check permission?| CHECK{Authorization
check}
CHECK -->|No check!| DB[(Database<br/>returns any user)]
DB --> LEAK[Data exposed<br/>IDOR vulnerability]
subgraph Fix - RBAC
REQ2[Request] --> AUTHZ[Check: does user<br/>own or have role<br/>for this resource?]
AUTHZ -->|Yes| ALLOW[Allow]
AUTHZ -->|No| DENY[403 Forbidden]
end
style LEAK fill:#f85149,color:#fff
style ALLOW fill:#238636,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Hiding links in the UI instead of enforcing authorisation server-side — obscurity is not access control.
- Checking authentication (are you logged in?) but not authorisation (are you allowed to do this?).
- Using sequential IDs and assuming users will not guess other users' resource IDs (IDOR).
- Applying access control in middleware but bypassing it in API routes or admin controllers.
Code Examples
// Access control only in the UI — not enforced server-side:
router()->get('/admin/users', [AdminController::class, 'index']);
// Middleware checks role for /admin/* in the UI
// But direct API call to /api/users/export has no role check
// Attacker bypasses UI, calls API directly — full data exposure
// RBAC check on every protected route:
public function show(int $id): Response {
$resource = Resource::findOrFail($id);
// Check ownership OR admin role:
if ($resource->user_id !== auth()->id() && !auth()->user()->isAdmin()) {
abort(403, 'Forbidden');
}
return response()->json($resource);
}
// Or policy-based (Laravel):
public function show(User $user, Resource $resource): bool {
return $user->id === $resource->user_id || $user->isAdmin();
}
// Controller: $this->authorize('show', $resource);