HIGH privilege escalationexpress

Privilege Escalation in Express

How Privilege Escalation Manifests in Express

In an Express application, privilege escalation typically occurs when an endpoint that should be restricted to a privileged role (e.g., admin) can be accessed by a lower‑privileged user due to missing or flawed authorization checks. Because Express is unopinionated about authentication and authorization, developers often implement role checks ad‑hoc, which creates opportunities for bypass.

Common Express‑specific patterns include:

  • Hard‑coded role strings in route handlers – a developer writes if (req.user.role === 'admin') { … } but forgets to apply the same check to all HTTP verbs (GET, POST, PUT, DELETE) on the same path, allowing a privileged action via a less‑protected verb.
  • Missing middleware ordering – placing a role‑check middleware after a body‑parser or after a route that already performs sensitive logic means the request reaches the handler before the check runs.
  • Over‑reliance on URL parameters for authorization – using req.params.userId to decide if a user can modify a resource without verifying that the userId matches the authenticated user’s identity or that the user has the required role.
  • Prototype pollution leading to role manipulation – as seen in CVE‑2020-28477, a polluted Object.prototype can cause req.user.role to evaluate to 'admin' for any user when the code uses Object.assign or spread syntax without proper validation.

These issues map directly to the OWASP API Top 10 category BFLA (Broken Function Level Authorization) and are exactly what middleBrick’s “BFLA/Privilege Escalation” check looks for during its unauthenticated black‑box scan.

Express‑Specific Detection

Detecting privilege escalation in Express requires observing how the application responds to requests that vary only in the claimed role or user identifier. middleBrick performs this by:

  • Scanning the target URL without any credentials (black‑box) and issuing a series of probes that modify headers, query strings, or JSON bodies to simulate different privilege levels (e.g., adding a custom X-User-Role: admin header or altering userId parameters).
  • Comparing the responses: if a request that should be forbidden (e.g., a DELETE to /admin/users/123) returns a success status (2xx) for a low‑privilege probe while the same request with a legitimate admin token returns a success, the scanner flags a potential BFLA.
  • Checking for inconsistent verb handling: the scanner sends GET, POST, PUT, PATCH, and DELETE to the same endpoint and looks for any verb that bypasses a role check present on others.
  • Looking for signs of prototype pollution: it sends payloads designed to pollute Object.prototype (e.g., {"__proto__":{"role":"admin"}}) and monitors whether the application’s behavior changes in a way that elevates privileges.

Because middleBrick runs 12 checks in parallel, the privilege‑escalation test completes within the 5‑15 second window alongside the other scans (authentication, BOLA/IDOR, input validation, etc.). The resulting report includes a severity rating, a short description of the vulnerable path, and remediation guidance that references Express‑specific fixes.

You can invoke this check from the CLI with:

middlebrick scan https://api.example.com

Or add it to your CI pipeline via the GitHub Action:

- name: Run middleBrick security scan
  uses: middlebrick/action@v1
  with:
    api-url: https://staging.example.com
    fail-below: B   # fail the job if score drops below B

The same scan can be launched directly from an AI coding assistant using the MCP Server integration, allowing developers to see findings without leaving their IDE.

Express‑Specific Remediation

Fixing privilege escalation in Express relies on applying consistent, centralized authorization middleware and validating that role information cannot be tampered with. Below are practical, Express‑native approaches.

1. Centralize role checks with middleware Place a single middleware that verifies the user’s role before any route handler runs. This guarantees that all verbs and all paths under a given router share the same check.

// authMiddleware.js
function requireRole(allowedRoles) {
  return function (req, res, next) {
    if (!req.user || !req.user.role) {
      return res.status(401).json({ error: 'Unauthenticated' });
    }
    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden: insufficient role' });
    }
    next();
  };
}

module.exports = { requireRole };

Then use it in your route definitions:

const express = require('express');
const { requireRole } = require('./authMiddleware');

const router = express.Router();

// All admin routes protected by the same middleware
router.use('/admin', requireRole(['admin']));

router.get('/admin/users', (req, res) => {
  // safe to assume req.user.role === 'admin'
  res.json({ users: getAllUsers() });
});

router.delete('/admin/users/:id', (req, res) => {
  deleteUser(req.params.id);
  res.status(204).send();
});

module.exports = router;

2. Validate the source of role information If you rely on a JWT or session, ensure the role claim is verified by the signature and never accepted from untrusted inputs like query strings or headers. Example using express-jwt:

const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');

const checkJwt = jwt({
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksUri: 'https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json'
  }),
  audience: 'YOUR_API_IDENTIFIER',
  issuer: `https://YOUR_AUTH0_DOMAIN/`,
  algorithms: ['RS256']
});

app.use(checkJwt);
// after this, req.user is guaranteed to be signed by the IdP

3. Avoid prototype‑pollution vectors Never merge user‑provided objects directly into objects that hold security‑relevant data. Use a whitelist or a library like lodash.mergeWith with a customizer that ignores __proto__ and prototype keys.

const _ = require('lodash');

function safeUpdateUser(update) {
  // Only allow specific fields
  const allowed = ['name', 'email'];
  const filtered = _.pick(update, allowed);
  return _.assign(req.user, filtered);
}

app.put('/profile', (req, res) => {
  safeUpdateUser(req.body);
  res.sendStatus(200);
});

4. Leverage Express Router for scoped middleware Define a separate router for each privilege level and mount it under a path that reflects that level. This makes it harder to accidentally expose a privileged route without the intended middleware.

const adminRouter = express.Router();
adminRouter.use(requireRole(['admin']));
adminRouter.get('/stats', (req, res) => { /* … */ });

app.use('/api/admin', adminRouter);

By applying these patterns, you close the most common Express‑specific privilege‑escalation routes that middleBrick would flag, turning a potential BFLA finding into a clean A‑grade score.

Frequently Asked Questions

Does middleBrick modify my Express code to fix privilege‑escalation issues?
No. middleBrick only scans the unauthenticated attack surface and reports findings with severity and remediation guidance. It does not inject patches, middleware, or any code changes into your application.
Can I use the middleBrick CLI to scan an Express API that runs locally on port 3000?
Yes. Run middlebrick scan http://localhost:3000 from your terminal. The CLI will send the same set of probes as the web dashboard and return a JSON or text report you can integrate into scripts or CI pipelines.