你是否在代码审计时对 Java 的反序列化漏洞感到困惑?或者听说过 Log4j、Fastjson 的严重漏洞,却不清楚它们背后的攻击原理?本文将采用“是什么-为什么-怎么做”的结构,带你系统性地理解 Java 反序列化漏洞的成因、分类及防御之道。
揭开反序列化的神秘面纱
首先,我们需要明确序列化与反序列化的基本概念。
- 序列化 (Serialization):指将程序中的 Java 对象转换为可以存储或传输的字节序列的过程。
- 反序列化 (Deserialization):是序列化的逆过程,将接收到的字节序列还原为内存中的 Java 对象。
这个过程就像是数据的“打包”与“拆包”。以下是一个简单的示例:
// 这是一个可以被序列化的用户类
public class User implements java.io.Serializable {
private String name;
private int age;
// ... 构造函数、getter/setter等 ...
}
// 序列化:将User对象“打包”成字节流
FileOutputStream fos = new FileOutputStream("user.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(new User("Manus", 2));
oos.close();
// 反序列化:从文件“拆包”,还原成User对象
FileInputStream fis = new FileInputStream("user.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
User user = (User) ois.readObject();
ois.close();
如何快速识别序列化数据?
- 魔法数字:Java 原生序列化后的字节流通常以十六进制
ac ed 00 05 开头。
- Base64 编码特征:对其 Base64 编码后,常以
rO0AB 开头。
危险从何而来:反序列化漏洞的核心
反序列化本身是 Java 的正常功能,其危险性源于程序对输入数据的过度信任。攻击者可以构造一个恶意的字节流,其中隐藏着精心设计的“利用链”。当服务器程序对此数据执行反序列化时,链中的恶意代码就会被触发执行,可能导致:
- 远程代码执行 (RCE):攻击者获得服务器控制权。
- 权限提升与数据泄露:服务器被植入后门,敏感数据被盗。
- 服务中断:通过恶意操作破坏系统稳定性。
这好比收到一个看似普通的快递包裹,拆开瞬间却弹出一个破坏性装置。
漏洞分类与攻防实践
Java 反序列化漏洞主要分为两大类:原生类反序列化漏洞和第三方组件反序列化漏洞。
1. 原生类反序列化漏洞
这类漏洞源于 JDK 自带的类库。
ObjectInputStream.readObject(): 最核心的反序列化方法,是实现 java.io.Serializable 接口的类进行反序列化的入口。
XMLDecoder: 用于 XML 格式数据的反序列化,解析不受信 XML 输入时存在风险。
SnakeYaml 的 Yaml.load(): 强大的 YAML 解析库,处理恶意 YAML 内容时可能触发漏洞。
2. 第三方组件反序列化漏洞
在现代开发中,大量使用的开源第三方库成为了漏洞重灾区。
| 组件名称 |
简介 |
历史漏洞查询 |
常见触发函数 |
| Log4j |
Apache 日志记录框架 |
阿里云漏洞库-Log4j |
logger.error(), logger.info() |
| Shiro |
Java 安全框架 |
阿里云漏洞库-Shiro |
CookieRememberMeManager |
| Jackson |
JSON 解析器 |
阿里云漏洞库-Jackson |
readValue() |
| XStream |
XML 序列化/反序列化库 |
阿里云漏洞库-XStream |
fromXML() |
| Fastjson |
阿里巴巴 JSON 解析库 |
阿里云漏洞库-Fastjson |
parseObject(), parse() |
这些组件为了便捷的数据交换功能而引入了反序列化机制,却也扩大了攻击面,是Java安全领域需要重点关注的对象。
深入剖析:JNDI 注入攻击
在众多高阶攻击中,JNDI 注入是实现 RCE 的关键手段。
- JNDI (Java Naming and Directory Interface):Java 命名和目录接口,充当访问各种命名和目录服务的“中介”。
- 攻击原理:攻击者利用反序列化漏洞,诱使服务器通过 JNDI 去查询一个由攻击者控制的恶意服务(如 RMI 或 LDAP)。该服务返回一个指向恶意类的引用,服务器加载并实例化该类,从而执行攻击代码。
JDK 的防御与绕过:一场持续的拉锯战
Java 官方不断修补 JNDI 注入的漏洞,但攻击技术也在迭代更新。
- JDK 6u45 / 7u21 后:默认禁止通过 RMI 远程加载类文件。
- JDK 6u141 / 7u131 / 8u121 后:增加了对 RMI、CORBA 协议远程类加载的限制,攻击者转向 LDAP 协议。
- JDK 6u211 / 7u201 / 8u191 后:对 LDAP 协议的远程类加载也进行了限制。
然而,安全研究员随后又发现了在特定条件下绕过这些限制的方法,攻防对抗从未停止。
审计要点与实战工具
了解了原理,我们来看如何在实战中应用。
代码审计关键点 (Sink Points)
在审计 Java 代码时,如果发现以下函数在处理用户可控的、不受信任的输入,必须高度警惕:
// 1. JDK 原生反序列化
ObjectInputStream.readObject()
// 2. XMLDecoder 反序列化
XMLDecoder.readObject()
// 3. Yaml 反序列化
Yaml.load()
// 4. XStream 反序列化
XStream.fromXML()
// 5. Jackson 反序列化
ObjectMapper.readValue()
// 6. Fastjson 反序列化
JSON.parse()
JSON.parseObject()
// 7. Shiro 反序列化 (通常在 rememberMe Cookie 中)
CookieRememberMeManager
// 8. Log4j (JNDI注入)
logger.error()
logger.info()
掌握这些关键入口点是进行有效代码审计的基础。
必备实战工具
- ysoserial:生成各种 Java 反序列化利用链(Gadget Chains)的经典工具。
- JNDI-Injection-Exploit:用于快速搭建恶意 JNDI 服务,配合利用链进行攻击测试。
- Yakit:一体化的网络安全单兵作战平台,集成多种测试功能。
- JYso:功能强大的反序列化利用工具。
- java-chains:收集和整理各种 Java 反序列化利用链的研究项目。
核心要点与思考
核心要点总结:
- 本质:漏洞根源在于程序反序列化了不可信的字节流,并执行了其中隐含的恶意操作链。
- 分类:分为 JDK 原生漏洞和更常见的第三方组件漏洞。
- 关键技术:JNDI 注入是实现远程代码执行的高效手段。
- 防御:升级组件、过滤输入、使用白名单、替换安全的序列化方案(如 JSON)是主要防护思路。
既然反序列化风险如此之高,为什么我们不能直接禁用?因为在分布式通信、会话持久化、缓存存储等众多业务场景中,对象的序列化与反序列化是实现数据交换和状态保持不可或缺的技术。关键在于如何安全地使用它,例如对反序列化过程施加严格的类型白名单控制。
希望本文能帮助你构建起对 Java 反序列化漏洞的系统性认知。安全之路漫长,保持学习与交流至关重要,欢迎在云栈社区与更多开发者一同探讨技术细节与防护实践。
|