Insecure Deserialization with Bearer Tokens
How Insecure Deserialization Manifests in Bearer Tokens
Bearer tokens are commonly used to convey claims between parties. While the token itself is usually a base64url‑encoded JSON (JWT) or an opaque string, some applications mistakenly treat the decoded payload as a serializable object and feed it into language‑specific deserialization routines. When this happens, an attacker who can craft a malicious token can trigger arbitrary code execution on the server.
Typical vulnerable code paths include:
- Python applications that
base64.b64decode(token)then pass the result topickle.loads()oryaml.load(..., Loader=yaml.FullLoader)without restricting the loader. - Java services that decode a token and feed the bytes to
ObjectInputStream.readObject()(often seen in legacy SSO integrations). - .NET APIs that use
BinaryFormatter.Deserializeon the token payload after base64 decoding. - Node.js code that mistakenly uses
vm.runInThisContextorevalon the decoded JSON string, which can lead to prototype pollution or code injection if the payload is not strictly validated.
Real‑world examples: CVE‑2015-7501 (Apache Commons Collections) showed how a deserialization gadget chain can be triggered via a crafted serialized object. When a Bearer token is used as the vehicle for that object, the attack becomes possible without any authentication step. Similarly, CVE‑2017-5638 (Apache Struts2) demonstrated OGNL expression injection via deserialized data; an attacker could embed a malicious OGNL expression in a token and achieve remote code execution on any endpoint that blindly deserializes the token.
Because Bearer tokens are often transmitted in the Authorization: Bearer <token> header, the attack surface is entirely unauthenticated, making insecure deserialization a critical risk for APIs that expose such endpoints.
Bearer Tokens-Specific Detection
Detecting insecure deserialization in Bearer tokens requires observing whether the server attempts to reconstruct objects from the token payload. middleBrick’s Input Validation check includes active probes that send specially crafted tokens designed to trigger known deserialization gadget chains.
When you submit a URL to middleBrick, the scanner:
- Extracts any
Authorization: Bearerheader pattern from the target’s responses. - Generates a series of tokens where the payload is base64url‑encoded versions of known malicious serialized objects (e.g., Java
Serializablegadgets, Pythonpicklepayloads, .NETBinaryFormatterstreams). - Sends each token in a request to the endpoint and monitors for error messages, delayed responses, or side‑effects that indicate successful object reconstruction (such as unexpected file writes, network calls, or changes in response status).
- Correlates findings with the token’s format: if the endpoint accepts a JWT but the server still attempts to deserialize the payload, the finding is flagged as “Insecure Deserialization – Bearer Token”.
Because the scan is black‑box and requires no credentials, the test works even when the token is opaque to the scanner; the scanner simply varies the token value and watches for behavioural changes. middleBrick reports the finding with a severity rating, the exact token value that triggered the behavior, and remediation guidance pulled from the OWASP API Security Top 10 (category A8:2019 – Injection).
Example of a finding in the middleBrick dashboard:
Finding: Insecure Deserialization in Bearer Token
Severity: High
Endpoint: https://api.example.com/auth/validate
Details: Server responded with 500 Internal Error after receiving a token containing a Java serialized gadget (base64 of `rO0ABXQAVg==`). This indicates the server attempted ObjectInputStream.readObject() on the token payload.
Remediation: Avoid deserializing token payloads; use a verified JWT library and validate the signature before accessing claims.
Bearer Tokens-Specific Remediation
The safest approach is to never deserialize the token payload with language‑specific object deserialization mechanisms. Instead, treat the token as a string, verify its integrity and authenticity, then extract claims using a purpose‑built library.
Below are language‑specific examples that show the correct flow.
Node.js (jsonwebtoken)
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
function verifyBearerToken(req, res, next) {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return res.status(401).send('Missing or malformed token');
}
const token = auth.slice(7); // remove 'Bearer '
try {
const payload = jwt.verify(token, SECRET, { algorithms: ['HS256'] });
// payload is a plain JSON object; no further deserialization needed
req.user = payload;
next();
} catch (err) {
return res.status(401).send('Invalid token');
}
}
Python (PyJWT)
import jwt
SECRET = 'your-256-bit-secret'
ALGORITHMS = ['HS256']
def verify_bearer_token(token: str):
if not token:
raise jwt.exceptions.InvalidTokenError('Token missing')
try:
payload = jwt.decode(token, SECRET, algorithms=ALGORITHMS)
# payload is a dict; no pickle or yaml load
return payload
except jwt.PyJWTError as exc:
raise jwt.exceptions.InvalidTokenError(str(exc))
# Usage in a Flask view
# auth_header = request.headers.get('Authorization')
# if auth_header and auth_header.startswith('Bearer '):
# token = auth_header.split()[1]
# user = verify_bearer_token(token)
Java (auth0/java-jwt)
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
private static final Algorithm ALG = Algorithm.HMAC256("secret");
public static java.util.Map<String, Object> verifyBearerToken(String token) {
if (token == null || !token.startsWith("Bearer ")) {
throw new IllegalArgumentException("Missing Bearer token");
}
String jwt = token.substring(7);
try {
com.auth0.jwt.interfaces.DecodedJWT verified = JWT.require(ALG)
.build()
.verify(jwt);
return verified.getClaims(); // Map of claim names to values
} catch (JWTVerificationException e) {
throw new IllegalArgumentException("Invalid token", e);
}
}
C# (System.IdentityModel.Tokens.Jwt)
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
private static readonly string Secret = "your-256-bit-secret";
private static readonly SymmetricSecurityKey Key =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret));
public static JwtSecurityToken ValidateBearerToken(string token)
{
if (string.IsNullOrWhiteSpace(token) || !token.StartsWith("Bearer "))
throw new ArgumentException("Missing Bearer token");
var jwtToken = token.Substring("Bearer ".Length);
var validationParams = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = Key,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
var handler = new JwtSecurityTokenHandler();
var principal = handler.ValidateToken(jwtToken, validationParams, out var validatedToken);
// validatedToken is a JwtSecurityToken; Claims are accessible via principal.Claims
return (JwtSecurityToken)validatedToken;
}
Key takeaways:
- Never pass the decoded token to
pickle.loads(),ObjectInputStream,BinaryFormatter, or similar APIs. - Always verify the token’s signature (or MAC) using a trusted key before accessing any claims.
- Restrict accepted algorithms to a safe list (e.g., only HS256 or RS256).
- If your system must carry complex data, place it in a separate, encrypted payload or a server‑side session store rather than inside the token.