Safeguard
Application Security•

CWE-285 Improper Authorization: Missing Checks and How to Fix

CWE-285 is a missing or wrong authorization check when someone accesses a resource. See real CVEs, a code example, test steps, and a central policy fix.

Safeguard Security Team
6 min read

CWE-285, Improper Authorization, is a product that does not perform, or performs incorrectly, an authorization check when an actor tries to access a resource or perform an action.

Authentication asks who you are. Authorization asks whether you may do this. This weakness is about the second question, and it is one of the most common serious findings in web applications and APIs. MITRE lists web server and database server technologies among the platforms.

It affects anyone who builds an endpoint that returns or changes data: API authors, plugin developers, back office tool teams, and anyone who wrote an admin script because it was quicker than a policy.

How it happens

The check is missing on one path, applied to the wrong object, or based on something the user controls. MITRE's examples show the typical shapes:

  • CVE-2024-6845 (Gold): a chatbot WordPress plugin does not perform authorization on a REST endpoint, allowing retrieval of an API key.
  • CVE-2022-24730 (Gold): a Go based continuous deployment product does not check that a user has certain privileges to update or create an app, allowing adversaries to read sensitive repository information.
  • CVE-2009-3168 (Gold) and CVE-2009-2960 (Gold): a web application does not restrict access to admin scripts, allowing authenticated users to reset administrative passwords or modify other users' passwords.

Notice the last pair. The users were logged in. Authentication worked. Nothing asked whether they were allowed to use admin scripts.

A minimal vulnerable API:

// Vulnerable: any logged-in user can read any invoice by ID
app.get("/api/invoices/:id", requireLogin, async (req, res) => {
  const invoice = await db.invoices.findById(req.params.id);
  res.json(invoice);
});

The route checks that someone is logged in. It never checks that the invoice belongs to them.

Another common form is a check in the client only:

// Vulnerable: the button is hidden, the endpoint is open
if (user.isAdmin) showDeleteButton();
// server: DELETE /api/users/:id has no role check

What an attacker gains

MITRE lists three groups of consequences:

  • Reading data. An attacker can read sensitive data from a store that is not restricted, or by calling privileged functionality that exposes it.
  • Modifying data. They can write to data stores or call privileged functions that write.
  • Gaining privileges and executing commands. When checks are not applied consistently, or at all, an attacker may gain privileges and execute unauthorized code or commands by changing or reading critical data.

The CVE examples above map onto these: an API key retrieved, repository information read, passwords reset.

How to find it

MITRE notes that static analysis is useful for detecting commonly used idioms for authorization, and a tool may analyze related configuration files such as .htaccess for Apache. Dynamic analysis may find many or all interfaces that do not require authorization, but manual analysis is needed to decide whether the lack of authorization is a problem. Manual techniques such as penetration testing and threat modeling are also listed.

A practical test plan:

  1. Inventory endpoints. From routes, an OpenAPI file, or traffic captures.
  2. Use two accounts. Log in as user A, copy a request that touches A's object, replay it as user B. If B gets the data, you have found it.
  3. Strip the session. Replay each request with no credentials and with a low privilege account.
  4. Try the admin routes as a normal user. Directly, not through the UI.
  5. Change IDs. Increment or swap identifiers in paths, query strings, and bodies.
  6. Review code for the pattern. A route handler with a login check but no ownership or role check.

Try it yourself

The demo below walks through an API scan that reports endpoints reachable without the right authorization. Use it as a model for the replay tests above.

Click through it at your own pace, or open it full screen.

How to fix it

The goal is a design where forgetting a check is hard, not a code base where every developer must remember one.

  • Deny by default. A route with no declared policy refuses access.
  • Centralize the decision. One policy function or middleware answers "may this actor do this to that resource". Handlers call it.
  • Check the object, not only the user. Verify ownership or membership of the specific resource, as well as the role.
  • Enforce on the server. Hiding a button is not authorization.
  • Re-check on every request. Do not trust a flag set earlier in the session unless it is protected and current.
  • Test the negative cases. Write automated tests that assert that user B cannot read user A's data and that a normal user cannot call admin routes.

Fixed version of the invoice endpoint:

app.get("/api/invoices/:id", requireLogin, async (req, res) => {
  const invoice = await db.invoices.findById(req.params.id);
  if (!invoice || !policy.can(req.user, "read", invoice)) {
    return res.sendStatus(404);
  }
  res.json(invoice);
});

Returning 404 for both missing and forbidden objects avoids confirming that an ID exists.

If the weakness is in a third party component, your own code is clean and the flaw ships in a dependency. In that case you need to know whether you use the affected package at all. The free in-browser scan at gold.safeguard.sh/scan lists known issues for up to 100 dependencies, with no login, and the file stays on your machine. Reachability analysis then shows whether your code actually calls the vulnerable function, which helps decide what to patch first.

Quick check

How can an authenticated user still trigger CWE-285?

Authentication only proves identity. If the server does not also check that this identity may use the resource or function, a logged-in user can reach things outside their role, as in the admin script CVEs.

Why test with two accounts?

Replaying one user's request as another user is the fastest way to reveal missing ownership checks, since the request is valid and only the authorization decision is wrong.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.