Bleichenbacher Attack in Flask with Bearer Tokens
Bleichenbacher Attack in Flask with Bearer Tokens — how this specific combination creates or exposes the vulnerability
A Bleichenbacher attack is a cryptographic padding oracle exploit first described for RSA PKCS#1 v1.5. In a Flask API that uses Bearer Tokens for authentication, the vulnerability surface arises when token validation or related crypto operations expose timing or error-behavior differences that an attacker can probe. For example, if your Flask service validates JWTs or opaque Bearer Tokens using a library that reveals whether a padding or signature check failed earlier than a full failure, an attacker can iteratively craft tokens and measure response behavior to gradually decrypt or forge tokens without knowing the key.
Consider a Flask route that accepts a Bearer Token in the Authorization header and passes it to a cryptographic verification routine. If the routine returns distinct errors for invalid padding versus invalid signature, or if HTTP responses differ in timing or content between "malformed token" and "invalid signature", the endpoint acts as a padding oracle. An attacker can automate requests with modified token ciphertexts, using adaptive chosen-ciphertext techniques to learn the plaintext token or recover the signing key material. This is especially relevant when tokens are encrypted or signed server-side using RSA, and the server does not use constant-time verification or proper error handling.
In practice, a misconfigured Flask app might decode a Base64-encoded Bearer Token and attempt RSA decryption with PKCS1v15 padding. If an attacker can observe HTTP status codes, response body differences, or timing discrepancies, they can mount a Bleichenbacher-style adaptive attack to recover the plaintext or forge a valid token. Common triggers include verbose error messages returned to the client, non-constant-time verification, and endpoints that process tokens differently depending on internal decryption or signature states.
Flask apps that integrate third-party identity providers or custom token services are at risk if the token validation logic leaks information. For instance, returning 401 with 'Invalid token padding' versus 'Invalid token signature' gives an attacker a practical oracle. Even when tokens are opaque strings, if the backend performs cryptographic operations to validate them and the validation path is distinguishable by timing or errors, the API becomes susceptible to this class of attack.
To assess such risks, scans test unauthenticated endpoints that accept Authorization headers and inspect whether responses and timing are consistent across valid and invalid tokens. Findings may highlight missing constant-time checks, verbose errors, or routes that process Bearer Tokens through non-hardened crypto code. Remediation involves ensuring token validation is performed in a way that does not leak distinguishable states, combined with secure cryptographic practices that remove the oracle from the request path.
Bearer Tokens-Specific Remediation in Flask — concrete code fixes
Remediation focuses on making token validation constant-time and ensuring errors do not reveal internal state. Below are concrete Flask examples that demonstrate insecure patterns and their hardened replacements.
Insecure pattern: timing- and error-leaking validation
from flask import Flask, request, jsonify
import time
import hashlib
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
from cryptography.hazmat.primitives.serialization import load_pem_public_key
app = Flask(__name__)
# Insecure: returns different messages and can be vulnerable to Bleichenbacher-style oracles
def verify_token_insecure(token_b64, public_key_pem):
try:
public_key = load_pem_public_key(public_key_pem.encode())
# Simulate decryption/verification with PKCS1v15
token = token_b64.decode('base64') # illustrative only
public_key.verify(
token['signature'],
token['data'],
asym_padding.PKCS1v15(),
hashes.SHA256()
)
return True, 'valid'
except ValueError as e:
# Different error types can leak info
if 'padding' in str(e).lower():
return False, 'padding_error'
return False, 'invalid'
@app.route('/api/protected')
def protected():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return jsonify(error='missing_token'), 401
token = auth[7:]
public_key_pem = open('public_key.pem').read()
ok, msg = verify_token_insecure(token, public_key_pem)
if not ok:
return jsonify(error=msg), 401
return jsonify(ok=True)
The insecure example above can expose timing differences and distinct error messages that an attacker can use as an oracle. The following hardened pattern addresses these issues.
Hardened pattern: constant-time validation and uniform errors
import hmac
import hashlib
import secrets
from flask import Flask, request, jsonify
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
from cryptography.hazmat.primitives.serialization import load_pem_public_key
app = Flask(__name__)
def verify_token_secure(token_b64, public_key_pem):
"""
Perform constant-time token validation where possible.
Always return a uniform result and avoid leaking padding vs signature issues.
"""
try:
public_key = load_pem_public_key(public_key_pem.encode())
token = token_b64.decode('base64') # illustrative only
# Use constant-time comparison for any derived values if applicable
public_key.verify(
token['signature'],
token['data'],
asym_padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception:
# Always return the same generic failure; avoid branching on error type
return False
@app.route('/api/protected')
def protected():
auth = request.headers.get('Authorization', '')
if not auth.lower().startswith('bearer '):
# Use a fixed delay or constant work to reduce timing distinguishability
secrets.token_bytes(32) # simulate constant work
return jsonify(error='invalid_token'), 401
token = auth[7:]
public_key_pem = open('public_key.pem').read()
ok = verify_token_secure(token, public_key_pem)
# Always sleep a small constant amount to reduce timing differences
secrets.token_bytes(16) # illustrative constant work
if not ok:
return jsonify(error='invalid_token'), 401
return jsonify(ok=True)
Key remediation practices:
- Use constant-time cryptographic APIs and avoid branching on error type (e.g., padding vs signature).
- Return a uniform error response and status code (e.g., 401 with {"error": "invalid_token"}) for all token failures.
- Introduce small constant-time delays or work to reduce timing distinguishability, but do not rely on delays alone.
- Ensure tokens are validated using hardened libraries and avoid homegrown crypto; prefer established JWT libraries with secure defaults.
- Rotate keys and use strong key sizes; monitor for unusual request patterns that may indicate probing.
These changes reduce the risk of Bleichenbacher-style oracles by eliminating distinguishable states and ensuring that error information does not assist an attacker in adaptive chosen-ciphertext probes against Bearer Token validation.