Flask Insecure Deserialization Vulnerability

Critical Risk Deserialization
FlaskPythonDeserializationRemote Code ExecutionpickleUnsafe DeserializationCode Injection

What it is

Application deserializes untrusted data using potentially unsafe methods like pickle, allowing attackers to execute arbitrary code through crafted serialized payloads.

from flask import Flask, request import pickle import base64 @app.route('/load_data', methods=['POST']) def load_data(): # Vulnerable: Deserializing user input with pickle data = request.form.get('data') decoded = base64.b64decode(data) obj = pickle.loads(decoded) # RCE vulnerability return f'Loaded: {obj}' @app.route('/session_load') def load_session(): # Vulnerable: Loading pickled session data session_data = request.cookies.get('session') session = pickle.loads(base64.b64decode(session_data)) return f'Welcome {session["username"]}' @app.route('/restore_object') def restore_object(): # Vulnerable: Unpickling file uploads file = request.files['data_file'] obj = pickle.load(file) # Arbitrary code execution return 'Object restored'
from flask import Flask, request import json import hmac import hashlib from itsdangerous import Serializer, BadSignature # Use a secret key for signing SECRET_KEY = 'your-secret-key' s = Serializer(SECRET_KEY) @app.route('/load_data', methods=['POST']) def load_data(): # Secure: Use JSON instead of pickle try: data = request.get_json() # Validate data structure if not isinstance(data, dict): return 'Invalid data format', 400 return f'Loaded: {data}' except ValueError: return 'Invalid JSON', 400 @app.route('/session_load') def load_session(): # Secure: Use signed serialization try: session_data = request.cookies.get('session') session = s.loads(session_data) # Verifies signature return f'Welcome {session["username"]}' except BadSignature: return 'Invalid session', 401 @app.route('/restore_object') def restore_object(): # Secure: Use JSON with validation try: file = request.files['data_file'] data = json.load(file) # Safe JSON deserialization # Validate expected structure required_fields = ['type', 'value'] if not all(field in data for field in required_fields): return 'Invalid data structure', 400 return 'Object restored safely' except json.JSONDecodeError: return 'Invalid JSON format', 400

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Flask views deserialize user data with pickle: data = pickle.loads(request.data). Pickle can execute arbitrary code during deserialization through __reduce__ method. Attackers craft malicious pickled objects executing commands. Common in session data, cache systems, or API endpoints accepting serialized data.

Root causes

Using pickle.loads() on Untrusted User Input Data

Flask views deserialize user data with pickle: data = pickle.loads(request.data). Pickle can execute arbitrary code during deserialization through __reduce__ method. Attackers craft malicious pickled objects executing commands. Common in session data, cache systems, or API endpoints accepting serialized data.

Deserializing YAML with yaml.load() Without SafeLoader

Code loads YAML without safe loader: data = yaml.load(request.data). Default loader executes Python objects and functions. Attackers inject !!python/object/apply to execute arbitrary code. Unsafe YAML deserialization enables remote code execution through specially crafted YAML payloads with Python object constructors.

Using eval() or exec() on User-Controlled Serialized Data

Application uses eval() to deserialize: data = eval(request.form['data']). eval() executes arbitrary Python code. Attackers inject malicious expressions: __import__('os').system('command'). Even with ast.literal_eval() safer, regular eval()/exec() on user input provides direct code execution vector.

Deserializing with jsonpickle, dill, or marshal on Untrusted Data

Using unsafe serialization libraries: jsonpickle.decode(user_data) or marshal.loads(). These libraries deserialize Python objects including code. Similar to pickle, enable arbitrary code execution. marshal designed for internal use, not external data. jsonpickle encodes objects but inherits pickle's security issues.

Not Validating or Signing Serialized Data Before Deserialization

Applications deserialize without signature verification: pickle.loads(base64.b64decode(cookie)). No HMAC or signature checking. Attackers modify serialized data, inject malicious payloads. Even with encryption, missing authentication allows tampering. Unsigned serialized data from users should never be deserialized.

Fixes

1

Never Use pickle, marshal, or dill on Untrusted Data

Avoid pickle entirely for user data. Use JSON instead: data = json.loads(request.data). JSON safe for deserialization, only supports data types, no code execution. For complex objects, use schema validation with marshmallow or pydantic. JSON prevents all deserialization attacks.

2

Use yaml.safe_load() Instead of yaml.load() for YAML Data

Always use SafeLoader: data = yaml.safe_load(request.data). SafeLoader restricts to basic YAML types, prevents object instantiation. Rejects !!python tags. Or use yaml.load(data, Loader=yaml.SafeLoader). Eliminates code execution risk while supporting YAML parsing for configuration and data.

3

Implement HMAC Signatures for All Serialized Data

Sign serialized data with itsdangerous: from itsdangerous import Serializer; s = Serializer(SECRET_KEY); token = s.dumps(data); verified_data = s.loads(token). Library handles signing and verification. Prevents tampering. Use URLSafeSerializer for tokens. Signatures ensure data integrity before deserialization.

4

Use JSON with Schema Validation for Structured Data

Combine JSON with schema validation: from marshmallow import Schema, fields; schema = MySchema(); data = schema.load(json.loads(request.data)). Define strict schemas with types, required fields, validation rules. Reject invalid data. Schema validation provides type safety and prevents unexpected data structures.

5

Never Use eval() or exec() on User Input

Avoid eval()/exec() entirely. For safe evaluation of literals, use ast.literal_eval(): import ast; data = ast.literal_eval(user_string). literal_eval() only evaluates literals: strings, numbers, lists, dicts. No code execution. For complex logic, use proper parsers and interpreters, never eval().

6

Implement Allowlists for Deserializable Classes if pickle Required

If pickle absolutely necessary, restrict classes: class SafeUnpickler(pickle.Unpickler): def find_class(self, module, name): if (module, name) not in ALLOWED_CLASSES: raise pickle.UnpicklingError(); return super().find_class(module, name). Allowlist prevents arbitrary object instantiation. Still risky, prefer JSON.

Detect This Vulnerability in Your Code

Sourcery automatically identifies flask insecure deserialization vulnerability and many other security issues in your codebase.