CWE-284: Improper Access Control
Overview
Improper access control is MITRE's broadest, top-level term for any failure that lets a user reach data or functionality they should not be able to reach. It is a Pillar weakness - the umbrella over the entire access-control family. Its two largest branches are CWE-287 (Improper Authentication, failing to correctly verify who is making the request) and CWE-285 (Improper Authorization, failing to correctly enforce what an already-identified user is allowed to do), and there are several more alongside them, covering privileges, ownership and request origin. Real findings are almost always one of those descendants rather than this entry, and it is the descendant pages that carry the concrete guidance.
Relationship to Other CWEs
CWE-284 is a MITRE Pillar, and MITRE's mapping guidance is Discouraged - real-world findings should map to a more specific descendant instead. MITRE's own note nominates seven better targets - CWE-862, CWE-863, CWE-732, CWE-306, CWE-1390, CWE-286 and CWE-923 - and pages here cover the first four, along with several of their relatives. How this page and those descendants relate:
- CWE-284 (this page) - the pillar over the whole family, covering any failure that lets a user reach data or functionality they should not be able to reach.
- CWE-287 (Improper Authentication) and CWE-285 (Improper Authorization) - the two largest children of CWE-284, covering identity verification and permission enforcement respectively. Both are themselves marked Discouraged, so prefer one of their children where the finding is specific enough. Under CWE-287, CWE-306 (Missing Authentication for Critical Function) is the one MITRE nominates here.
- CWE-862 (Missing Authorization) and CWE-863 (Incorrect Authorization) - the two children of CWE-285 that most findings resolve to. In CWE-862 no permission check runs on the path; in CWE-863 a check runs and its logic is wrong.
- CWE-269 (Improper Privilege Management), CWE-282 (Improper Ownership Management) and CWE-346 (Origin Validation Error) - three more direct children, for privilege assignment and drop, file/resource ownership, and request-origin trust respectively.
- CWE-749 (Exposed Dangerous Method or Function) - also a direct child, for privileged APIs, RPC endpoints, or scripting bridges reachable without an access check.
- CWE-639 and its variant CWE-566 - further down the tree under CWE-863, for trusting a user-controlled object key (IDOR).
- CWE-732 (Incorrect Permission Assignment) and CWE-926 (Improper Export of Android Application Components) - both children of CWE-285.
Credential weaknesses are a separate branch that MITRE files elsewhere in the research view: CWE-522 (Insufficiently Protected Credentials) sits under CWE-668 and CWE-1390, and CWE-798 (Use of Hard-coded Credentials) under CWE-1391. Both appear beneath CWE-287 in MITRE's simplified mapping view (view-1003), which MITRE publishes for categorising published vulnerability data such as NVD's, so a finding whose CWE came from a CVE record may route them here.
Use this page only for general access-control hygiene when a finding is reported generically, without a more specific CWE.
OWASP Classification
A01:2025 - Broken Access Control
Risk
High: Access control failures enable unauthorized access to sensitive functionality and data:
- Horizontal privilege escalation: Users access other users' data (view other customers' orders, read others' emails)
- Vertical privilege escalation: Regular users gain admin privileges (delete users, modify system settings)
- Data breaches: Access to PII, financial records, or health information beyond what a user is authorized to see
- Business logic bypass: Circumventing payment, approval workflows, or rate limits
- Compliance violations: Unauthorized data access can violate GDPR, HIPAA, SOC 2
Remediation Steps
Core Principle: Access control must be explicit and deny-by-default across every entry point and resource; never let untrusted input or client-side flow determine authentication or authorization.
Trace the Data Path
- Source: The identity or permission claim behind the request - a session, token, cookie, or role/permission asserted by the client.
- Sink: The protected resource or operation - an endpoint, function, file, or record being read, modified, or invoked.
- Missing Controls: No server-side check that the request is authenticated (CWE-287) and, separately, that the authenticated identity is authorized for this specific resource and action (CWE-285).
Enforce Authentication and Authorization on Every Request (Primary Defense)
- Authenticate the user: verify identity via a server-validated session/token; missing or invalid credentials return
401 Unauthorized. - Load the user's roles/permissions from a trusted, server-side source - never from client-supplied data.
- Check permission for the specific action:
if not userHasPermission(action): return 403 Forbidden. - Enforce object-level authorization, not just function-level. Prefer making ownership part of the question -
WHERE id = ? AND owner_id = ?, a repository method requiring both, a queryset already scoped to the caller - rather than a check applied to the row after it is fetched, so a route that forgets the check is still confined to rows the caller could have seen. Give the "not yours" and the "does not exist" outcomes the same response. - Only then perform the action.
Default Deny, Not Default Allow
Explicitly require authentication and authorization for every route; treat "public" as the exception that must be marked, not the default:
VULNERABLE - allowlist of protected paths (easy to miss a new one):
if path == "/admin" or path == "/settings":
requireAuth()
SECURE - default deny, explicit public exceptions:
PUBLIC_PATHS = ["/login", "/register", "/public/*"]
for every request:
if path not in PUBLIC_PATHS:
requireAuthentication()
requireAuthorization()
Apply Additional Protections (Defense in Depth)
- Centralize the checks: Use middleware, decorators, or a shared authorization service - checks scattered across handlers are how gaps happen.
- Use indirect object references: UUIDs or session-scoped opaque IDs reduce enumeration, but never replace the object-level authorization check itself.
- Apply least privilege: Grant only the permissions a role needs; review and revoke unused grants on a cadence appropriate to risk.
- Monitor and audit: Log authorization decisions and permission changes (safe metadata only), and alert on repeated 403s or privilege-escalation attempts.
Test the Fix
- Horizontal: as User B, attempt to read, modify, and delete User A's resources by ID - every attempt must be denied.
- Vertical: as a regular user, attempt admin endpoints directly (URL, API call) and by tampering with a role claim in a token or cookie.
- Confirm unauthenticated requests get
401. For an authenticated-but-unauthorized request that names no particular record - an admin route, a privileged action -403is the right answer. Where the request names an object, the refusal for someone else's object and the refusal for an ID that exists in no row must be identical in status and body, or the pair enumerates the table one request at a time; see CWE-639. - Re-scan with the security scanner to confirm the finding is resolved.
Common Vulnerable Patterns
// VULNERABLE - no authentication or authorization check
function adminDeleteUser(userId):
database.deleteUser(userId) // reachable by anyone, any role
Why this is vulnerable: the function is named for an administrative action and there is nothing between the caller and the deletion. No identity is established, no role is consulted, and the resource identifier arrives from the request - so the control is not weak or bypassable, it is absent. Every statement present is correct; what is missing was never written, so there is no wrong line for a reviewer or a scanner to find.
Because this is the pillar of the family, a finding filed here is a starting point rather than a diagnosis. The useful next question is which of the three things is missing, because they have different fixes: nobody proved who the caller is (CWE-306 where no check exists on the path, CWE-287 where one exists and is bypassable), nobody checked what that caller may do (CWE-862, or CWE-863 where the check runs and decides wrongly), or the check ran and did not cover this particular object (CWE-639). Answering that determines whether the fix belongs in middleware, in the handler, or in the query - and a fix applied at the wrong one of those layers leaves the endpoint reachable by a route that skips it.
Secure Patterns
// SECURE - authenticate, then authorize the specific action
function adminDeleteUser(userId, request):
currentUser = authenticate(request) // 401 if missing/invalid
if not currentUser.hasRole("ADMIN"):
return 403
database.deleteUser(userId)
Why this works: The operation is unreachable until the caller's identity is verified server-side, and unreachable a second time unless that identity carries the specific permission the action requires. Neither check can be skipped by a client that edits a request, disables JavaScript, or guesses a URL, because both are enforced on the server rather than inferred from anything the client sent.
Common Pitfalls
- Treating "authenticated" as "authorized": Assuming that because a request passed login/session checks, the user is automatically allowed to perform the requested action - authentication only proves identity; a separate, explicit permission check is still required for every action and object.
- Fixing the flagged endpoint without auditing its siblings: Adding a missing check to the reported
GETroute while thePUT/DELETE/export route for the same resource is left unchecked - access control gaps rarely exist in isolation. - Mapping every access-control finding to this page instead of the specific weakness: CWE-284 is too broad to give the concrete fix; identify whether the gap is really CWE-285 (authorization), CWE-287 (authentication), or one of their more specific descendants, and use that page's guidance.
- Relying on an unlisted or hard-to-guess URL as the control: An admin path that isn't linked from the UI is still reachable by anyone who requests it directly; obscurity is not a substitute for a server-enforced identity and permission check.