Broken Object Level Authorization (BOLA / IDOR)
ID |
broken_object_level_authorization |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Family |
API1:2023 - Broken Object Level Authorization |
CWE |
CWE-639, CWE-284 |
Resource |
authorization |
Language |
any (model-level) + Java / C# (AST Phase C) + Python / JS / Go / PHP (regex Phase B) |
Description
Reports API endpoints that access objects by an ID-shaped path or query parameter (/orders/{id}, /users/:id, /documents?id=…) and require authentication, but show no model-level evidence that the handler verifies the caller’s ownership of the referenced object.
This is the classic Insecure Direct Object Reference (IDOR) pattern and the OWASP API1:2023 — Broken Object Level Authorization — entry. Per-language source-walking (Phase B) can lift the verdict to HIGH when no explicit ownership check is found in the handler body; AST-based confirmation (Phase C) extends the check across one-hop helper calls and ORM-side row scoping in Java and C#.
Rationale
Authentication confirms who the caller is. Object-level authorization is the orthogonal question — which objects is this caller allowed to see or change?
An endpoint that returns the order whose id is in the URL — without verifying the order belongs to the caller — lets any authenticated user enumerate every order in the system simply by incrementing the id. BOLA is the most-reported finding in public API breach catalogs (OWASP API1 since 2019 and unchanged at the top in 2023). Real-world consequences include cross-tenant data leaks in B2B SaaS, mass enumeration of medical records, and credential / token theft via account-recovery flows that fetch by user id.
Authorization is not "did I write a check?" but "does the check actually scope by user identity?" — a frequent failure mode is a guard that confirms the caller is logged in (already enforced by auth) without comparing the resource owner to the caller, leaving BOLA wide open.
Remediation
The fix is to bind every object-by-id access to the caller’s identity at the data layer or via an explicit guard. In order of robustness:
-
Repository-scoped queries — the strongest. Replace
findById(id)withfindByIdAndOwner(id, currentUser)(Spring Data),Model.objects.filter(user=request.user).get(pk=id)(Django),findOne({_id: id, userId: req.user.id})(Mongoose),Where("user_id = ?", userID).First(&order, id)(GORM), orOrder::where('user_id', auth()→id())→findOrFail($id)(Eloquent). The caller cannot escape the filter. -
Policy / authorization framework —
@PreAuthorize("@bookService.canRead(#id, principal)"), ASP.NETIAuthorizationService.AuthorizeAsync(user, resource, policy), Laravel$this→authorize('view', $order), DRFcheck_object_permissions(request, obj). -
Explicit guard — load the object, compare
obj.userId == currentUser.id, return 403 / 404 on mismatch. Acceptable but easy to skip on a refactor; prefer the patterns above.
Avoid relying on "the user only sees their own ids in the UI" — assume any authenticated caller will iterate the id space.
Configuration
The detector recognises a configurable set of id-shaped parameter patterns (id, *Id, *_id, uuid) — patterns can be extended via the idParameterPatterns property. The model-level pass is the cheap floor; per-language Phase B / Phase C source walking is automatic when the handler file is parseable.
Unless your project uses unusual id-parameter naming, you typically do not need to configure this detector.
References
-
OWASP API Security Top 10 (2023) - API1:2023 - Broken Object Level Authorization.
-
CWE-639 : Authorization Bypass Through User-Controlled Key.
-
CWE-284 : Improper Access Control.
-
OWASP Cheat Sheets Series: Authorization Cheat Sheet.
-
OWASP Cheat Sheets Series: Insecure Direct Object Reference Prevention Cheat Sheet.