java安全漫谈-反序列化-1
引言
在之前的RMI篇中,我们观察到RMI通信的核心是对象的序列化与反序列化。反序列化漏洞在安全领域声名显赫,几乎每种语言都曾因此受累。那么,一个核心问题是:为什么反序列化操作如此危险?
简单来说,当程序需要将网络或磁盘上的数据“还原”成一个内存中的对象时,如果这个“还原”过程本身可以被操纵,去执行攻击者预期的逻辑,漏洞就产生了。
漏洞根源
为了理解漏洞的根源,我们首先要明白不同序列化方案的设计思路:
**通用数据格式 (如 JSON/XML)**:
- 目标:跨语言、跨平台通信。
- 局限:通常只支持基本数据类型(字符串、数字、布尔等)。要传输一个“对象”,需要额外约定或使用扩展库(如Jackson/Fastjson)。
**语言原生序列化 (如 Java
Serializable)**:- 目标:完美地在网络间传输一个编程语言中的“对象”。
- 特点:能将对象的完整状态(属性、类信息等)转化为字节流。接收方可以完整地“还原”出一个对象。
- 风险:这个“还原”过程非常强大,而强大往往伴随着危险。
关键认知:“反序列化漏洞”是一个泛指,不同序列化库(Jackson, Fastjson)或不同语言(Java, PHP, Python)的漏洞成因和利用方式可能截然不同。本文我们聚焦于 Java 原生的 readObject 机制。
Java与PHP的设计差异
为了理解Java反序列化的独特风险,一个极佳的方式是与PHP进行对比。许多人认为Java的 readObject 等同于PHP的 __wakeup,但这是一个常见的误解。
| 特性 | PHP __wakeup |
Java readObject |
|---|---|---|
| 核心职责 | 对象初始化 | 对象重建 |
| 执行时机 | 在PHP引擎自动完成反序列化,恢复所有属性之后执行。 | 反序列化过程的核心逻辑本身。它定义了如何从流中读取数据来还原对象。 |
| 开发者控制力 | 弱。你无法干预数据如何被解析成属性。 | 极强。你可以完全自定义从流中读取什么数据以及如何设置对象状态。 |
| 类比 | 新房交付后的“精装修”。房子结构(属性)已建好,你只是做些后期布置。 | 从零开始盖房子的“施工图”。图纸规定了如何打地基、砌墙、布线(即如何读取数据并构建对象)。 |
这个设计差异是导致Java反序列化漏洞多的根本原因!
以上是P牛的意思,但是我仍然认为不准确
ObjectInputStream.readObject()(反序列化入口)
能 完整控制 序列化协议的解析;决定如何读取流、如何解析类结构、对象如何创建、字段如何恢复;是整个序列化协议的 核心实现者
但: 用户不能自定义它(除非写子类 ObjectInputStream——但这种方式实际上非常有限,不能破坏协议结构)
用户类的 readObject()只能在 defaultReadObject() 恢复字段之后,进行“扩展读取”和“额外逻辑”。
不能更改底层序列化协议的解析方式;不能改变类的字段如何映射、对象如何创建;不能跳过 defaultReadObject() 并用自定义协议替代(完全不行)
PHP与Java的反序列化流程
1. PHP反序列化:以 __wakeup 为例
PHP的反序列化流程是“黑盒”的:
1 | 序列化数据 -> (PHP引擎自动解析) -> 对象属性已赋值 -> 调用 `__wakeup`(如果存在) |
__wakeup 通常用于处理引擎无法自动完成的工作,最经典的例子是重新连接资源(如数据库连接)。
1 |
|
结论:PHP反序列化漏洞很少由 __wakeup 直接引发,更多的是因为反序列化后我们能控制对象的属性,进而导致在 __destruct 析构函数或其他方法中被利用。
2. Java反序列化:灵活的 readObject
Java则将反序列化的巨大权力交给了开发者。一个类可以实现私有的 readObject 方法,完全掌控如何从 ObjectInputStream 中重建自己。
1 | public class Person implements Serializable { |
在这个例子中,readObject 不仅仅是在“还原”对象,它还在执行业务逻辑(打印消息、检查密码)。如果攻击者能够构造一个恶意的序列化流,让 name 为 “admin”,他就能在反序列化过程中直接窃取到密码。
这种“在反序列化过程中执行业务逻辑”的能力,是Java反序列化漏洞的温床。 大量的Java库(如Apache Commons Collections, XStream等)在其类的 readObject 方法中实现了复杂且可能被利用的逻辑,形成了所谓的“利用链”(Gadget Chain)。
Python的反序列化
作为对比,Python的 pickle 反序列化机制更为“狂野”。它本质上是一个小型的字节码解释器,反序列化过程直接执行 pickle 字节码。这意味着攻击者几乎可以直接编码任意Python命令在目标机器上执行,无需依赖现有的类和方法(Gadget)。从危害性上讲,Python反序列化通常是最大的。
总结
- PHP:反序列化是“自动化的属性还原 + 可选的初始化”。漏洞多在生命周期函数(如
__destruct)中。 - Python:反序列化是“一个虚拟机的执行”。漏洞是直接的代码执行。
- Java:反序列化是“一个由
readObject方法定义的、可能包含任意逻辑的对象重建过程”。这正是Java反序列化漏洞如此普遍和灵活的根源。


