Désérialisation non sécurisée : risques et défense
Risques de pickle, Java Serialization et PHP unserialize avec des données non fiables, et moyens de les remplacer ou limiter.
💡 Explication simple (ELI5)
La sérialisation ressemble à l’envoi d’instructions de montage d’une maquette. La désérialisation suit ces instructions pour reconstruire les données. Certains formats peuvent aussi nommer des classes ou appeler des hooks pendant la reconstruction ; accepter des instructions non fiables peut déclencher un comportement inattendu. L’impact dépend du format, du runtime, des classes accessibles et du contexte applicatif.
Core Concepts & Key Terms
- Sérialisation / désérialisation
- Conversion de données ou d’objets en représentation stockée ou transmise, puis analysée ou reconstruite par le programme.
- Hooks de désérialisation
- Callbacks comme Python __reduce__, Java readObject ou PHP __wakeup / __unserialize, susceptibles de s’exécuter pendant la reconstruction.
- Gadget chain
- Enchaînement de comportements de bibliothèques déjà présentes pouvant produire un effet inattendu. Sa disponibilité dépend de l’application et de ses versions.
- Filtre de désérialisation
- Politique limitant les classes ou métriques du graphe lors de la lecture d’objets. Elle réduit le risque sans rendre les flux objets natifs non fiables préférables.
How Unsafe Deserialization Can Become an Exploit Chain
Des octets non fiables atteignent le décodeur
Un cookie, corps d’API, message de file ou enregistrement contient des données influençables par un attaquant.
L’application appelle un format objet natif
Le code transmet ces octets à un décodeur comme Python pickle, Java ObjectInputStream ou PHP unserialize().
La reconstruction déclenche le comportement du format
Selon le format et les classes accessibles, constructeurs, hooks, autoloading ou méthodes de bibliothèques peuvent s’exécuter avant la validation applicative.
L’impact dépend du comportement accessible
Un gadget ou hook utilisable peut produire des effets non autorisés ; d’autres données échouent ou consomment seulement des ressources. L’exécution de code n’est pas automatique.
Python / Flask : chargement objet vulnérable et validation de JSON primitif
# VULNERABLE: pickle can execute behavior during unpickling.
from flask import Flask, request
import pickle
app = Flask(__name__)
@app.post("/api/session")
def load_session():
# Never deserialize attacker-controlled bytes with pickle.
session = pickle.loads(request.get_data())
return {"user_id": session.user_id}
# SAFER: parse JSON primitives, then validate expected shape.
from flask import Flask, abort, request
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024
@app.post("/api/session")
def load_session():
payload = request.get_json()
if not isinstance(payload, dict):
abort(400, "Expected a JSON object")
user_id = payload.get("user_id")
if type(user_id) is not int or user_id <= 0:
abort(400, "Invalid user_id")
# Resolve authorization and session state server-side.
return {"user_id": user_id}
Liste de vérification de sécurisation
JSON nécessite aussi des limites de taille et une validation de structure. Pour les anciens formats objets, ne vous fiez pas à une simple liste de classes : authentifiez les octets exacts avant désérialisation et protégez les clés. Un MAC ne chiffre pas les données et n’empêche pas le rejeu.
- Ne désérialisez pas de flux objets natifs non fiables avec Python pickle ou PHP unserialize ; préférez des données JSON primitives avec validation de schéma et de taille.
- Si la sérialisation Java ne peut pas encore être supprimée, configurez une allowlist ObjectInputFilter et des limites de graphe ; le filtre n’est pas activé automatiquement partout.
- Pour protéger l’intégrité de données héritées, authentifiez les octets exacts avant désérialisation avec une clé MAC bien protégée ; un MAC ne chiffre pas et n’empêche pas le rejeu.
- Gardez l’autorité de session côté serveur ou utilisez un mécanisme de jeton signé maintenu, avec expiration, audience et rotation de clés.