Java 序列化机制 — Dubbo 漏洞的根基
什么是序列化
序列化(Serialization):将 Java 对象转换为字节流,可以存储到文件或通过网络传输
反序列化(Deserialization):将字节流还原为 Java 对象
类比理解
序列化 = 把一个乐高模型拆成零件装进盒子,附上说明书
反序列化 = 按照说明书把零件重新组装成模型
为什么 Dubbo 需要序列化?
Dubbo 是 RPC 框架,Consumer 调用 Provider 的方法时,参数和返回值需要通过网络传输
传输前必须序列化为字节流,接收后反序列化还原
Java 原生序列化
核心接口:Serializable
1 | // 一个类只要实现 Serializable 接口就可以被序列化 |
Serializable 是一个标记接口(没有任何方法),仅仅告诉 JVM “这个类可以被序列化”
序列化过程
1 | // 序列化:对象 → 字节流 |
序列化数据格式
Java 序列化数据以固定的 magic number 开头:AC ED 00 05
可以用 SerializationDumper 工具查看二进制结构
1 | $ java -jar SerializationDumper.jar -f user.ser |
安全意义:看到 aced0005 开头的数据 = 这里有 Java 反序列化,可能有漏洞
危险的 readObject() 方法
自动调用机制
当调用 ObjectInputStream.readObject() 时,如果被反序列化的类定义了 private void readObject(ObjectInputStream in) 方法,JVM 会自动调用它
这是整个 Java 反序列化漏洞存在的根本原因
为什么危险?
1 | public class EvilClass implements Serializable { |
反序列化时,数据来自攻击者,代码在服务器执行
攻击者可以控制对象的字段值 → 影响 readObject() 中的逻辑 → 达成任意代码执行
完整 Demo:反序列化触发命令执行
1 | import java.io.*; |
现实中的攻击场景
攻击者不能直接控制类的代码
上面的 Demo 是为了说明原理,实际攻击中,攻击者不可能让服务器有一个 readObject() 直接执行命令的类
但是! 服务器 classpath 中已有的第三方库可能包含”有副作用”的 readObject() 方法
这就是 Gadget Chain(利用链)的概念 — 下一节详解
哪些地方会触发反序列化?
Java RMI
JMX
RPC 框架(Dubbo、Hessian) ← 我们的重点
HTTP 请求中的 Java 序列化数据
消息队列(ActiveMQ、RabbitMQ)
Session 持久化
其他序列化方式简介
Java 原生序列化只是众多方式之一,Dubbo 还支持:
| 方式 | 特点 | 安全风险 |
|---|---|---|
| Java 原生 | 内置,功能完整,性能差 | 最高:完整 gadget chain 支持 |
| Hessian2 | Dubbo 默认,二进制紧凑 | 高:hashCode/toString 触发链 |
| Kryo | 高性能,需注册类 | 高:无内置安全过滤 |
| FST | 高性能 | 高:无内置安全过滤 |
| Fastjson | JSON 格式 | 中:autoType 漏洞 |
| Protobuf | 类型安全,跨语言 | 低:不支持任意类实例化 |
Hessian2 的反序列化机制和 Java 原生不同,将在后续笔记中详细分析
关键概念总结
| 概念 | 说明 |
|---|---|
| 序列化 | 对象 → 字节流 |
| 反序列化 | 字节流 → 对象 |
Serializable |
标记接口,表示类可序列化 |
readObject() |
反序列化时自动调用的魔法方法 |
aced0005 |
Java 序列化数据的 magic number |
| 攻击本质 | 数据来自攻击者 + 代码在服务器自动执行 |
动手练习
编译运行上面的 DeserDemo,观察计算器弹出
用 xxd user.ser | head 查看序列化文件的二进制开头,确认 aced0005
思考:如果 readObject() 不直接执行命令,而是调用了某个看似无害的方法(如 hashCode()),攻击者还能利用吗?→ 这就是 Gadget Chain 的起点
| 上一章 | 目录 | 下一章 |
|---|---|---|
| Dubbo漏洞 | 01-Gadget-Chain原理 |