Insecure Deserialization in Flask with Bearer Tokens
Insecure Deserialization in Flask with Bearer Tokens
Insecure deserialization occurs when an application processes untrusted serialized data without sufficient integrity checks. In a Flask API that uses Bearer tokens for authentication, the combination of session handling, token validation, and deserialization routines can inadvertently create pathways for attackers to manipulate object graphs, escalate privileges, or execute code.
Consider a Flask endpoint that receives an HTTP Authorization header containing a Bearer token and also processes a serialized payload (e.g., via form data, JSON body, or cookies). If the server deserializes user-controlled data—such as base64-encoded pickled objects, YAML, or even complex JSON structures mapped to Python classes—without validating integrity, an attacker can craft malicious serialized content. When the server reconstructs objects, it can trigger gadget chains that perform filesystem operations, execute commands, or alter application logic.
Flask itself does not enforce deserialization; patterns emerge from developer choices such as using pickle, yaml.load, or frameworks that rely on serialized session stores. If a Bearer token is accepted but the associated user context is derived from deserialized data (for example, embedding user roles or permissions inside a serialized object), tampering with that object can lead to Insecure Direct Object References (IDOR) or BOLA-style authorization issues. An attacker who can influence the deserialized content might forge tokens or session-like structures that the server trusts, bypassing intended access controls.
Real-world attack patterns mirror known CVEs affecting Python deserialization, where gadget chains via __reduce__ or __reduce_ex__ enable arbitrary code execution. Even without direct remote code execution, insecure deserialization can lead to data exposure, authentication bypass, or privilege escalation within the API’s authorization logic.
To illustrate a vulnerable pattern, the following example shows a Flask route that accepts a Bearer token in the Authorization header and deserializes a base64-encoded payload without validating its origin:
import base64
import pickle
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/profile', methods=['GET'])
def get_profile():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return jsonify({'error': 'missing_bearer'}), 401
token = auth.split(' ', 1)[1]
# Vulnerable: deserializing user-controlled data
data_b64 = request.args.get('data')
if data_b64:
raw = base64.b64decode(data_b64)
user_data = pickle.loads(raw) # Insecure deserialization
# Incorrectly trusting deserialized object for authorization decisions
if user_data.get('role') == 'admin':
return jsonify({'profile': 'admin_data'})
return jsonify({'profile': 'user_data'})
In this example, the Bearer token is only used for initial gatekeeping, while critical authorization decisions rely on deserialized data. An attacker can craft a malicious pickle payload that executes code during pickle.loads, or modifies role claims to escalate privileges. The presence of a Bearer token does not prevent this if the token and the deserialized object are not cryptographically bound.
Another common scenario involves signed JWT Bearer tokens paired with deserialized session data. If a server deserializes a user’s claims from an external source (e.g., a cached object or a serialized form) and merges it with token claims without strict validation, it may accept tampered permissions. This aligns with BOLA/IDOR patterns where an attacker manipulates identifiers or serialized attributes to access or modify other users’ resources.
Integrating security checks from a scanner like middleBrick can help detect such risky patterns by correlating OpenAPI/Swagger specifications with runtime behavior. Its LLM/AI Security checks also identify prompt injection or unsafe usage patterns in AI-integrated endpoints that may indirectly handle serialized data.
Bearer Tokens-Specific Remediation in Flask
Remediation focuses on eliminating deserialization of untrusted data and cryptographically binding Bearer tokens to authorization decisions. Avoid pickle, yaml.load, and similar constructs for untrusted inputs. Use safe deserializers such as json.loads for JSON data and enforce strict schema validation. Ensure that authorization is derived from the token itself—verified and parsed on each request—rather than from deserialized content.
Below are concrete code fixes for Flask APIs using Bearer tokens:
- Validate the Bearer token on every request and keep authorization claims within the token.
- Do not deserialize user-controlled data; if you must process structured input, use JSON with schema validation.
- Bind token metadata to server-side session or context to prevent tampering.
Secure Flask example with Bearer token handling and safe data handling:
import jwt
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = 'your_jwks_or_secret' # Use environment variables in production
def verify_token(token: str) -> dict:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
raise ValueError('token_expired')
except jwt.InvalidTokenError:
raise ValueError('invalid_token')
@app.route('/profile', methods=['GET'])
def get_profile():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return jsonify({'error': 'missing_bearer'}), 401
token = auth.split(' ', 1)[1]
claims = verify_token(token)
# Authorization based on token claims, not deserialized data
if claims.get('role') == 'admin':
return jsonify({'profile': 'admin_data'})
return jsonify({'profile': 'user_data'})
If you must process additional structured data, use a strict schema-based parser like marshmallow or pydantic instead of deserialization:
from pydantic import BaseModel, ValidationError
from flask import Flask, request, jsonify
app = Flask(__name__)
class SafeData(BaseModel):
role: str
user_id: int
@app.route('/submit', methods=['POST'])
def submit():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return jsonify({'error': 'missing_bearer'}), 401
token = auth.split(' ', 1)[1]
# verify_token as defined previously
claims = verify_token(token)
try:
safe_input = SafeData(**request.json)
except ValidationError:
return jsonify({'error': 'invalid_input'}), 400
# Use claims for authorization, safe_input for data
return jsonify({'ok': True, 'role': claims.get('role')})
These patterns ensure that Bearer tokens remain the source of truth for authorization while avoiding risky deserialization. middleBrick’s API security scans can surface endpoints that rely on deserialization and flag missing token-bind validation, helping teams prioritize fixes.