Privilege Escalation with Jwt Tokens
How Privilege Escalation Manifests in Jwt Tokens
JSON Web Tokens (JWT) are stateless credentials that carry claims about the subject, such as user ID, roles, or permissions. When an API trusts a JWT without properly validating its signature, claims, or algorithm, an attacker can forge or modify a token to gain higher privileges. This is a classic privilege‑escalation vector that maps to the OWASP API Top 10 category BFLA/Privilege Escalation and has been observed in real‑world vulnerabilities such as CVE-2016-10555 (the “none” algorithm attack).
Common JWT‑specific patterns that lead to privilege escalation include:
- Accepting tokens signed with the
nonealgorithm, allowing an attacker to strip the signature and inject arbitrary claims (e.g., changingrolefromusertoadmin). - Failing to validate the
aud(audience) oriss(issuer) claims, which lets a token issued for one service be replayed against another. - Using a weak or static secret (or public key) that can be brute‑forced, enabling the attacker to sign a new token with elevated claims.
- Missing expiration (
exp) or not‑before (nbf) checks, allowing an old token with privileged claims to be reused indefinitely. - Not verifying that the token’s
sub(subject) matches the authenticated user context, leading to token substitution attacks.
In code, these issues often appear where a JWT library is called with minimal options, trusting the token’s contents without explicit validation.
Jwt Tokens-Specific Detection
Security scanners that focus on JWT look for the absence of critical validation steps. middleBrick’s unauthenticated black‑box scan includes checks that map directly to these patterns:
- Algorithm validation – it attempts to replay a token with the
nonealgorithm and observes whether the endpoint accepts it. - Signature strength – it tests whether changing the payload and re‑signing with a guessed secret (dictionary attack) yields a valid response.
- Claim verification – it sends tokens with missing or altered
aud,iss,exp, androleclaims to see if the API enforces them. - Token replay – it captures a valid token and re‑uses it after its nominal expiry to test expiration handling.
When any of these tests succeed, middleBrick flags a finding under the "BFLA/Privilege Escalation" category, provides a severity rating (typically high), and includes the exact token payload that bypassed validation. The finding also references the relevant OWASP API Top 10 item and gives a short remediation hint.
Developers can reproduce the same checks locally using the middleBrick CLI:
# Install the CLI (npm) if not already present
npm i -g middlebrick
# Scan an API endpoint
middlebrick scan https://api.example.com/orders
The output is JSON or plain text and can be piped into CI pipelines. In a GitHub Actions workflow, the step would look like:
name: API Security Scan
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick scan
uses: middlebrick/action@v1
with:
api-url: https://staging.example.com
fail-below: B # fail job if score drops below B
The MCP Server integration lets you invoke the same scan from an IDE such as Claude or Cursor, giving immediate feedback while you edit API code.
Jwt Tokens-Specific Remediation
Fixing JWT‑based privilege escalation requires tightening validation on both the server side and, where applicable, the client side. The following code examples use the widely adopted jsonwebtoken for Node.js, but the principles apply to any language.
**1. Enforce a strong algorithm and reject none**
const jwt = require('jsonwebtoken');
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET; // must be sufficiently random
function verifyAccessToken(token) {
return jwt.verify(token, ACCESS_TOKEN_SECRET, {
algorithms: ['HS256'], // only allow HS256
audience: 'myapi', // match the intended audience
issuer: 'my-auth-service', // match the expected issuer
// expiration and not‑before are checked by default
});
}
**2. Validate custom claims after verification**
function authorizeAdmin(decoded) {
// Assuming the token contains a "role" claim
if (decoded.role !== 'admin') {
throw new Error('Insufficient privileges');
}
// additional checks: e.g., ensure the user ID matches the request context
return true;
}
// Usage in an Express route handler
app.get('/admin/data', (req, res) => {
const auth = req.headers.authorization;
if (!auth?.startsWith('Bearer ')) return res.sendStatus(401);
const token = auth.slice(7);
try {
const payload = verifyAccessToken(token);
authorizeAdmin(payload);
// proceed with privileged operation
res.json({ data: 'sensitive' });
} catch (err) {
res.sendStatus(403);
}
});
**3. Rotate secrets and use asymmetric keys for higher assurance**
When possible, switch from HMAC (shared secret) to RSA/ECDSA (public/private pair). The private key signs tokens; the public key verifies them, eliminating the risk of secret leakage.
const { privateKey, publicKey } = require('./keys'); // PEM strings
function signToken(payload) {
return jwt.sign(payload, privateKey, { algorithm: 'RS256', expiresIn: '15m' });
}
function verifyToken(token) {
return jwt.verify(token, publicKey, { algorithms: ['RS256'] });
}
**4. Short lifetimes and refresh‑token rotation**
Set exp to a few minutes for access tokens and use a single‑use refresh token stored server‑side. This limits the window an attacker can exploit a stolen token.
By applying these validation steps, you close the specific code paths that middleBrick’s scanner tests for, turning a potential privilege‑escalation finding into a hardened API endpoint.
Frequently Asked Questions
Does middleBrick modify or block malicious JWTs it detects?
Can I use middleBrick to verify that my JWT implementation enforces audience and issuer claims?
aud and iss claims and reports whether the endpoint rejects them, giving you concrete evidence of claim validation.