PHP序列化与反序列化

wen PHP项目 6

深入浅出PHP序列化与反序列化:原理、漏洞与实战防御

目录导读

  1. 什么是PHP序列化与反序列化?
  2. 序列化与反序列化的核心函数与格式解析
  3. 魔术方法:反序列化的隐形开关
  4. 反序列化漏洞(POP链)的成因与攻击场景
  5. 真实案例:从反序列化到远程代码执行
  6. 如何安全地使用序列化:防御策略清单
  7. 常见问题答疑(FAQ)

什么是PHP序列化与反序列化?

在Web开发中,我们经常需要将对象或数组转换为可存储、可传输的字符串形式,这个过程叫序列化(Serialization);而将字符串还原为原始对象或数组的过程则称为反序列化(Unserialization),PHP通过内置函数serialize()unserialize()完成这两个操作。

PHP序列化与反序列化

序列化让复杂的数据结构变得“扁平化”,便于存入Session、缓存、数据库或通过API传输,但这也是一把双刃剑——如果反序列化操作了不可信的用户输入,就会引发严重的安全问题。

序列化与反序列化的核心函数与格式解析

PHP语言中,serialize()会输出一种特定格式的字符串,一个对象User{ name:"admin"; isAdmin:false }会被序列化为:

O:4:"User":2:{s:4:"name";s:5:"admin";s:7:"isAdmin";b:0;}

解析这个格式:

  • O代表对象,4为类名长度,User为类名。
  • 2表示有2个属性。
  • s:4:"name"表示字符串属性名,长度为4。
  • b:0表示布尔值false。

还有i(整数)、a(数组)、N(NULL)等类型标识,理解此格式,是手动构造攻击载荷(Payload)的基础。

魔术方法:反序列化的隐形开关

PHP中的魔术方法(如__construct__destruct__wakeup__sleep等)在特定操作时自动执行,其中最关键的两个:

  • __sleep():在serialize()执行前调用,允许清理对象并返回要序列化的属性数组。
  • __wakeup():在unserialize()执行后立即调用,常用于重新建立数据库连接等恢复操作。

攻击视角:如果攻击者能控制传入unserialize()的字符串,他们就可以控制对象属性值,进而触发__wakeup()__destruct()中的危险方法(如文件删除、命令执行)。

反序列化漏洞(POP链)的成因与攻击场景

成因:当开发者将用户可控的输入(如Cookie、请求参数、签名数据)直接传递给unserialize()时,攻击者可以精心构造一个恶意对象链(Property-Oriented Programming,即POP链),通过一连串已存在的类方法逻辑,达到命令执行、文件读取、SSRF等目的。

典型攻击场景

  • 将序列化数据存入Cookie,读取时未验证来源。
  • 多语言交互中,从外部API获取数据后直接反序列化。
  • 使用unserialize()处理数据库中的持久化对象,但数据库被SQL注入篡改。

真实案例:从反序列化到远程代码执行

以经典的ThinkPHP 5.0.x反序列化RCE漏洞为例:

  • 攻击者找到一个接受data参数的入口,后端代码执行unserialize(base64_decode($data))
  • 攻击者借助框架中的Output类和Process类,构造一条以__destruct为起点的POP链。
  • 最终链中调用proc_open()执行系统命令,实现远程代码执行。

该案例说明,即使没有明显危险函数,攻击者也能利用框架内已有类的方法拼凑出攻击链。

如何安全地使用序列化:防御策略清单

  1. 永远不要反序列化不可信数据:这是最核心原则,如果可能,优先使用json_encode/json_decode替代。
  2. 使用allowed_classes参数unserialize($data, ['allowed_classes' => false])可禁止对象创建,或限制为白名单类。
  3. 进行HMAC签名校验:对序列化字符串附加签名,确保数据未被篡改。
  4. 升级框架与依赖库:及时修补已知POP链漏洞。
  5. 配置open_basedir与禁用危险函数:降低漏洞利用后的损害。

常见问题答疑(FAQ)

Q1:serialize()json_encode()有什么区别? A:serialize()能保留对象的类信息,但存在安全隐患;json_encode()仅保留公开属性且不触发魔术方法,更安全、跨语言兼容性更好。

Q2:为什么__wakeup()会绕过某些检查? A:因为__wakeup()在属性完全恢复后才被调用,攻击者可以利用此顺序,在检查逻辑执行前把恶意属性值写进对象。

Q3:如何快速检测一个反序列化漏洞? A:尝试将O:1:"A":0:{}这类测试对象传入入口,观察响应时间、错误信息或文件系统变化,配合工具(如PHPGGC)生成Payload进行验证。


PHP序列化与反序列化是强大的功能,但必须建立在“信任边界”清晰的架构上,开发者应时刻保持警惕:边界上的每一个输入都不可信,任何将字符串还原为对象的动作,都等同于执行了一段隐藏的代码逻辑。 通过本文的梳理,希望你能从原理到实战,全面加固自己的应用安全防线。

抱歉,评论功能暂时关闭!