Privilege Escalation with Bearer Tokens
How Privilege Escalation Manifests in Bearer Tokens
Bearer tokens are commonly used to convey identity and authorization information in APIs. When the token is not properly validated, an attacker can craft or modify a token to gain higher privileges than intended. This type of privilege escalation often appears in the following patterns:
- Token tampering with weak or missing signature verification: If an API accepts a JWT and only checks that the token is present (or verifies it with a weak secret), an attacker can flip the
algheader tononeor forge a signature using a guessed secret, then add claims such asrole: adminorscope: write:all. - Insufficient claim validation: Even when a signature is validated, APIs sometimes ignore critical claims like
exp(expiration),aud(audience),iss(issuer), or custom scope claims. An attacker can reuse an old token that still validates signature-wise but has expired, or present a token issued for a different service (audience mismatch) to escalate privileges. - KID header injection: Some libraries use the
kid(key ID) field to look up the verification key. If the key lookup is based on user‑controlled input without proper validation, an attacker can pointkidto a malicious key they control, thereby signing a token with elevated claims. - Token leakage and replay: Tokens logged in URLs, error messages, or stored insecurely can be captured and replayed. If the API does not enforce token binding (e.g., to a specific IP or client certificate), a stolen token can be used from another context to act with the victim’s privileges.
Real‑world examples include CVE‑2022‑23529 (JSON Web Token library accepting alg:none) and CVE‑2020‑15257 (JWT validation bypass via kid injection). Both allow an attacker to escalate from a low‑privilege user to administrative access by manipulating the Bearer token presented to the API.
Bearer Tokens-Specific Detection
middleBrick’s black‑box scanner includes a dedicated privilege‑escalation check that focuses on how Bearer tokens are handled. During the 5‑15 second scan it:
- Sends requests with no Authorization header to baseline the endpoint.
- Presents a series of crafted Bearer tokens:
- A valid token (if one can be discovered from public docs) to confirm normal behavior.
- A token with the
algheader set tononeand elevated claims (e.g.,role: admin). - A token signed with a weak secret (brute‑forced common strings like
secret,123456). - A token with an expired
expclaim but otherwise valid signature. - A token with a manipulated
kidheader pointing to an attacker‑controlled key URL. - A token with mismatched
audorissclaims. - Analyzes responses for changes in status code, returned data, or error messages that indicate the token was accepted and granted higher privilege (e.g., a 200 response with admin‑only data, or a change from 403 to 200).
- Correlates findings with the OpenAPI/Swagger spec (when available) to verify that the endpoint expects specific scopes or roles.
Example of using the middleBrick CLI to test an API:
# Install the CLI (npm)
npm i -g middlebrick
# Scan a target endpoint
middlebrick scan https://api.example.com/orders
# Sample JSON output (truncated for clarity)
{
"target": "https://api.example.com/orders",
"score": 42,
"grade": "F",
"categories": [
{
"name": "Privilege Escalation",
"severity": "high",
"findings": [
{
"id": "PE-BT-001",
"description": "Accepts JWT with alg:none and elevated role claim",
"evidence": "Request with header Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJyb2xlIjoiYWRtaW4ifQ. returned 200 with admin order list",
"remediation": "Verify JWT signature; reject tokens with alg:none"
}
]
}
]
}
If the scanner detects any of the above conditions, it flags a privilege‑escalation finding with severity ≥ medium, provides the exact request/response evidence, and links the issue to OWASP API Top 10 2023 A1: Broken Object Level Authorization (which includes vertical privilege escalation via token misuse).
Bearer Tokens-Specific Remediation
Fixing privilege‑escalation flaws in Bearer token usage requires proper validation of both the token’s integrity and its claims. Below are language‑agnostic principles followed by concrete code snippets for common stacks.
1. Verify the signature with a strong algorithm and key
Never accept unsigned tokens or rely on a secret that can be guessed. Use asymmetric keys (RS256, ES256) when possible, and if symmetric keys are used, ensure they are sufficiently long and random.
// Node.js – using jsonwebtoken with RS256
const jwt = require('jsonwebtoken');
const fs = require('fs');
const PUBLIC_KEY = fs.readFileSync('/etc/keys/public.pem', 'utf8');
function authenticate(req, res, next) {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return res.status(401).send({error: 'Missing token'});
}
const token = auth.slice(7);
try {
const payload = jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] });
// Attach payload to request for downstream use
req.user = payload;
next();
} catch (err) {
return res.status(401).send({error: 'Invalid token'});
}
}
2. Validate essential claims
After signature verification, check exp, nbf, aud, iss, and any application‑specific scopes or roles.
// Continuing the Node.js example
function authenticate(req, res, next) {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return res.status(401).send({error: 'Missing token'});
}
const token = auth.slice(7);
try {
const payload = jwt.verify(token, PUBLIC_KEY, {
algorithms: ['RS256'],
audience: 'https://api.example.com/',
issuer: 'https://auth.example.com/'
});
// Additional application checks
if (!payload.scope || !payload.scope.includes('orders:read')) {
return res.status(403).send({error: 'Insufficient scope'});
}
req.user = payload;
next();
} catch (err) {
return res.status(401).send({error: 'Invalid or expired token'});
}
}
3. Reject tokens with alg:none and verify KID handling
Explicitly refuse the none algorithm and ensure the kid field is resolved against a trusted key store, not used directly as a file path or URL.
// Python – using PyJWT with explicit algorithm list
import jwt
import os
PUBLIC_KEY = os.environ.get('JWT_PUBLIC_KEY')
def bearer_auth(token):
try:
# List of allowed algorithms; 'none' is omitted
payload = jwt.decode(token, PUBLIC_KEY, algorithms=['RS256'],
audience='https://api.example.com/',
issuer='https://auth.example.com/')
if 'orders:read' not in payload.get('scope', []):
raise jwt.InvalidTokenError('Missing scope')
return payload
except jwt.PyJWTError as e:
raise ValueError(f'Token validation failed: {e}')
# Usage in a Flask view
from flask import request, abort
@app.route('/api/orders')
def orders():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
abort(401)
token = auth.split()[1]
try:
user = bearer_auth(token)
except ValueError:
abort(401)
# proceed with user context
return {'orders': get_orders_for(user['sub'])}
4. Bind tokens to client context (optional but strong)
If feasible, include a hash of the client TLS certificate or a nonce in the token and verify it on each request. This mitigates replay attacks even if a token is stolen.
Applying these fixes eliminates the privilege‑escalation vectors that middleBrick’s scanner looks for, turning a failing grade (F) into a passing one (A‑C) for the Privilege Escalation category.