cyber / pentest-red-team
Exploitation web avancée : désérialisation insecure
Explication
Ce que vous allez apprendre
- Comprendre pourquoi désérialiser une donnée non fiable peut exécuter du code arbitraire
- Reconnaître les signes d'une désérialisation dangereuse en Python, Java, PHP et .NET
- Expliquer le mécanisme d'une "gadget chain" sans avoir besoin de l'exécuter
- Préférer des formats sans exécution de code (JSON) pour tout échange avec le client
- Signer et vérifier des données sérialisées quand le format binaire reste indispensable
Dans quel contexte ?
Après les classes de vulnérabilités classiques du Top 10 (leçon précédente), une application de lab expose un endpoint qui accepte un objet "sérialisé" en base64 dans un cookie de session personnalisée. Rien dans le code ne ressemble à une injection SQL ou une XSS — et pourtant, cette fonctionnalité en apparence anodine peut permettre une exécution de code à distance complète.
D'abord, il faut comprendre ce que la sérialisation fait réellement
Sérialiser un objet consiste à le transformer en une suite d'octets transportable (pour l'envoyer sur le réseau ou le stocker), puis à le reconstruire plus tard via la désérialisation. Le problème surgit quand le format de sérialisation permet d'encoder non seulement des DONNÉES, mais aussi un COMPORTEMENT à exécuter au moment de la reconstruction.
C'est exactement le rôle de la méthode __reduce__ en Python : elle définit ce qui doit être exécuté quand pickle reconstruit l'objet. Si cette reconstruction se fait sur une donnée entièrement contrôlée par un attaquant, ce comportement devient arbitraire.
| Langage | Fonction dangereuse sur une entrée non fiable | Équivalent sûr |
|---|---|---|
| Python | pickle.loads() | json.loads() |
| Java | ObjectInputStream.readObject() | Formats sans exécution (JSON, Protobuf) |
| PHP | unserialize() | json_decode() |
| .NET | BinaryFormatter.Deserialize() | System.Text.Json |
Prérequis
Cette leçon suppose une bonne compréhension des classes de vulnérabilités web de base (leçon précédente) ; la désérialisation insecure va plus loin en touchant à la structure même du code, pas seulement à une entrée de formulaire.
Une fois ce mécanisme compris en Python, il se généralise à d'autres langages sous des formes différentes
En Java, les attaques dites de "gadget chains" exploitent des bibliothèques déjà présentes sur le système (comme d'anciennes versions de Commons-Collections) pour enchaîner des appels de méthodes légitimes jusqu'à obtenir une exécution de commande arbitraire. L'outil ysoserial génère des payloads de démonstration précisément pour étudier ce mécanisme en environnement de lab dédié — jamais pour une exécution en dehors d'un tel cadre.
Piège fréquent
Beaucoup de développeurs pensent qu'un simple try/except autour de pickle.loads() protège contre ce risque. C'est faux : l'exécution du code malveillant se produit PENDANT la désérialisation elle-même, avant même que l'exception potentielle ne puisse être levée et interceptée.
Voici comment on résout ce problème à la racine
Bonne pratique
La meilleure protection reste structurelle : ne jamais désérialiser de format capable d'encoder du comportement (pickle, YAML non sécurisé, objets Java/.NET) à partir d'une entrée contrôlée par l'utilisateur. JSON, en tant que format de données pur, ne permet tout simplement pas ce genre d'attaque — c'est un choix d'architecture, pas un correctif ponctuel.
Le dernier cas à connaître : quand la sérialisation binaire reste indispensable
Si un besoin technique impose malgré tout un format binaire côté serveur, signer chaque objet sérialisé avec HMAC (comme dans l'exemple de cette leçon) permet de détecter toute modification avant même de tenter la désérialisation — à condition d'utiliser une comparaison à temps constant (hmac.compare_digest) pour éviter une fuite d'information par timing.
Cette classe de vulnérabilité comprise, la prochaine leçon aborde un terrain différent : les race conditions et les abus de logique métier, des failles qui n'ont souvent rien à voir avec une ligne de code buggée, mais avec une hypothèse incorrecte sur l'ordre d'exécution.
Commandes & code
Exploitation web avancée : désérialisation insecure
Quand une application désérialise des données contrôlées par l'utilisateur sans validation, elle peut exécuter du code arbitraire.
# Exemple pédagogique (Python) : pickle.loads() sur une donnée non fiable = exécution de code
import pickle
import base64
class Payload:
def __reduce__(self):
# __reduce__ définit ce qui sera exécuté à la désérialisation
import os
return (os.system, ("id > /tmp/preuve_lab.txt",))
malicious = base64.b64encode(pickle.dumps(Payload()))
print(malicious)
# Si le serveur fait pickle.loads(base64.b64decode(input_utilisateur)), le code s'exécute# La correction : ne JAMAIS désérialiser des données non fiables avec pickle/yaml.load
# Utiliser des formats de données purs (JSON) sans exécution de code possible
import json
data = json.loads(user_input) # pas d'exécution de code arbitraire possible avec json// Java : gadget chains avec des bibliothèques vulnérables (ex: Commons-Collections < correctif)
// ysoserial génère des payloads de démonstration pour des labs de test dédiés
// java -jar ysoserial.jar CommonsCollections6 'id' > payload.ser
// Objectif pédagogique : comprendre pourquoi ObjectInputStream.readObject() est dangereux# Signes qui doivent alerter lors d'un audit de code
- Utilisation de pickle.loads / yaml.load(Loader=yaml.Loader) sur une entrée utilisateur (Python)
- ObjectInputStream.readObject() sur un flux non signé (Java)
- unserialize() sur une donnée utilisateur sans __wakeup/__destruct sécurisés (PHP)
- BinaryFormatter.Deserialize() sur une entrée non fiable (.NET)# Mitigation robuste : signer/chiffrer les objets sérialisés côté serveur, jamais côté client
import hmac
import hashlib
SECRET = b"cle-secrete-longue-generee-aleatoirement"
def sign(data: bytes) -> bytes:
return hmac.new(SECRET, data, hashlib.sha256).digest()
def verify(data: bytes, signature: bytes) -> bool:
expected = sign(data)
return hmac.compare_digest(expected, signature) # comparaison à temps constantRésumé
- La désérialisation insecure permet une exécution de code à distance (RCE) sans "injection" classique.
- Le danger vient de la capacité du format à encoder un comportement, pas seulement une donnée.
- Préférer des formats sans exécution de code (JSON) pour tout échange avec le client.
- Si la sérialisation binaire est indispensable, signer/chiffrer et vérifier avant désérialisation.
Exercices pratiques
Mission : auditer un cookie de session suspect chez NordShield
Objectif : Diagnostiquer un risque de désérialisation insecure sur un endpoint de lab et proposer une remédiation architecturale, pas un simple correctif ponctuel.
Contexte
Une application de lab NordShield accepte un objet sérialisé en base64 dans un cookie personnalisé nommé session_data. Le code source montre un appel pickle.loads() sur ce cookie côté serveur, sans aucune validation préalable.