Java安全学习笔记3--反序列化篇1
该篇笔记由Gemini3 pro preview模型总结而来。
1. 什么是反序列化?
在网络通信中,JSON 和 XML 只能传递基本数据类型(字符串、整型等)。如果想传输一个复杂的**Java 对象**(包含属性、状态甚至逻辑),就需要将其转换为二进制流(序列化),接收方再将其还原为对象(反序列化)。- 序列化 (Serialization): 对象 -> 二进制流 (writeObject)
- 反序列化 (Deserialization): 二进制流 -> 对象 (readObject)
2. 跨语言反序列化对比 (核心理论)
理解不同语言的反序列化差异,是理解 Java 漏洞成因的关键。| 语言 | 机制 | 核心特点 | 漏洞成因 |
|---|---|---|---|
| Python (Pickle) | 基于栈的虚拟机 | 序列化数据里直接包含了指令(Opcode)。 | 最危险。只要能传数据,就能直接让虚拟机执行任意指令/代码。 |
| PHP (Unserialize) | 属性还原 | 侧重于还原数据属性。依赖 __wakeup (醒来) 和 __destruct (销毁)。 | 漏洞通常由魔术方法触发,属于“反序列化后”的初始化操作。 |
| Java (readObject) | 对象重建 | 侧重于自定义还原过程。允许开发者介入反序列化流程。 | 漏洞源于 readObject 方法。它不仅还原属性,还允许开发者在还原过程中执行自定义代码。 |
一句话总结: Java 的 readObject 倾向于解决“如何还原一个完整对象”的问题,给予了开发者极大的自由度,也埋下了隐患。
3. Java 序列化核心机制
3.1 两个关键方法
开发者可以在类中重写以下两个私有方法,来干预序列化过程:- writeObject(ObjectOutputStream s):
- 决定如何把对象写入流。
- 通常先调用 s.defaultWriteObject() 写入默认属性。
- 关键点:可以调用 s.writeObject() 写入额外的数据(私货)。
- readObject(ObjectInputStream s):
- 决定如何从流里恢复对象。
- 通常先调用 s.defaultReadObject() 恢复默认属性。
- 关键点:必须按照写入顺序,调用 s.readObject() 读取那些额外的数据,并进行处理。
3.2 代码示例 (夹带私货)
Java
1 | |
4. 序列化数据的内部结构 (底层协议)
通过 SerializationDumper 分析 16 进制数据,可以发现 Java 序列化流中有一个特殊的区域:
objectAnnotation (对象注解/附注)
- 定义:当类实现了 writeObject 方法时,它写入的所有自定义数据(即上面的“私货”),都会被存储在 TC_OBJECT 下面的 objectAnnotation 块中。
- 对比:
- classAnnotations: (RMI 篇提到) 存储类加载路径(Codebase URL),由 annotateClass 写入。
- objectAnnotation: 存储对象自定义数据,由 writeObject 写入。
为什么它很重要?
- HashMap 的例子:HashMap 并没有把键值对(Key-Value)当成默认属性存储,而是全写进了 objectAnnotation 里。
- 漏洞伏笔:反序列化 HashMap 时,readObject 会从这个区域读出 Key,并重新计算 Key 的 Hash 值。这一计算过程(hashCode() 方法的调用)是众多反序列化利用链(Gadget Chain)的第一张多米诺骨牌(如 URLDNS 链)。
5. 总结与审计思路
- 攻击面:任何接收二进制流并执行 readObject 的地方(RMI, JMX, WebLogic, Jenkins 等)。
- 利用核心:
- 攻击者构造恶意的序列化数据。
- 服务端在 readObject 还原对象时,自动触发了恶意的代码逻辑。
- 后续学习:
- 既然 HashMap 在反序列化时会重算 Hash,如果我把一个 URL 对象放进 HashMap 里,会发生什么?
- 这就是下一篇要学的 URLDNS 利用链。
复习小贴士:
只要看到 implements Serializable,就要条件反射地去找有没有 readObject 方法。如果有,仔细看它在还原对象时,是不是处理了什么不受信任的“私货”。
Java安全学习笔记3--反序列化篇1
https://rightevil.github.io/Java安全学习笔记3-反序列化篇1/