PHP序列化格式深度解析:原理、安全风险与最佳实践
目录导读
什么是PHP序列化
PHP序列化(serialization)是指将PHP变量(对象、数组、字符串等)转换为可存储或传输的字符串格式的过程,与之对应的操作是反序列化(unserialization),即从字符串恢复为原始变量。

核心作用:
- 持久化存储:将对象保存到文件或数据库
- 跨请求传递:在会话(Session)、缓存(如Redis)中传递复杂数据
- 远程通信:通过API传递结构化数据(尤其在低版本PHP中)
举例说明,一个简单的对象序列化后可能变成类似这样的一行字符串:
O:4:"User":2:{s:4:"name";s:5:"Alice";s:3:"age";i:25;}
PHP序列化格式详解
PHP序列化后的字符串具有严格的语法结构,按数据类型分为以下规则:
标量类型
| PHP类型 | 序列化格式 | 示例 |
|---|---|---|
| 布尔 | b:值 |
b:1 (true) / b:0 (false) |
| 整数 | i:值 |
i:42 |
| 浮点数 | d:值 |
d:3.14 |
| 字符串 | s:长度:"内容" |
s:5:"hello" |
| NULL | N; |
直接 N; |
复合类型
数组:a:元素个数:{键;值;...}
其中键可以是整数或短字符串:
a:3:{i:0;s:3:"php";i:1;s:4:"java";s:4:"lang";s:5:"python";}
对象:O:类名长度:"类名":属性数量:{属性名;值;...}
注意属性名包含可见性修饰符(PHP 5.3+):
O:4:"User":2:{s:8:"username";s:6:"admin";s:10:"password";s:6:"secret";}
对象序列化细节
private属性名会添加\0类名\0前缀(不可见字符)protected属性名添加\0*\0前缀
\0*\0_age表示protected的$_age
序列化与反序列化的实际应用
Session存储
// 默认PHP使用序列化存储session
session_start();
$_SESSION['user'] = new User('admin');
// 实际存储字符串:user|O:4:"User":1:{s:8:"username";s:5:"admin";}
但注意,PHP内置的session.serialize_handler 有两种格式:php(默认)和 php_serialize(兼容性更好,推荐使用)。
缓存与文件持久化
// 将复杂数据缓存到文件
$data = ['posts' => [...], 'stats' => [...]];
file_put_contents('cache.txt', serialize($data));
$cached = unserialize(file_get_contents('cache.txt'));
警告:绝不要对不可信的用户输入直接unserialize(),这是最危险的操作之一。
RPC或API数据交换(已过时)
早期PHP框架(如旧版ThinkPHP)使用序列化进行内部通信,如今已被JSON/XML替代,因为安全性和互操作性更差。
反序列化漏洞与防御
漏洞原理
当PHP调用unserialize()时,若传入的字符串被污染,攻击者可构造恶意序列数据,触发对象的__wakeup()、__destruct()、__toString()等魔术方法,甚至执行任意代码。
经典攻击链示例:
class Evil {
public $cmd;
public function __destruct() {
system($this->cmd);
}
}
// 攻击者构造序列化字符串:
// O:4:"Evil":1:{s:3:"cmd";s:6:"rm -rf";}
// 调用 unserialize($evil_string) 后自动执行__destruct
防御措施(按重要程度排序)
- 绝对不要反序列化不可信数据
即使使用了allowed_classes参数,也无法完全规避魔术方法触发。 - 使用JSON替代序列化(推荐)
json_encode/json_decode不执行代码,且跨语言兼容。 - 启用签名验证
在存入数据前使用hash_hmac签名,反序列化前验证:$data = serialize($obj); $signature = hash_hmac('sha256', $data, SECRET_KEY); // 存储时组合:$data . '|' . $signature - 限制类白名单(PHP 7.0+)
$obj = unserialize($input, ['allowed_classes' => ['MySafeClass']]);
- 禁用危险魔术方法
在关键类中移除或重写__wakeup、__destruct,或使用__wakeup校验属性是否合法。
常见问题问答(FAQ)
Q1:PHP序列化比JSON更快吗?
A:在纯PHP内部传递对象时,序列化确实比json_encode稍快(约10-20%),但差差距在多数场景可忽略,JSON的优势在于安全性、跨语言兼容性、可读性,因此建议优先用JSON。
Q2:我如何将对象序列化后安全地存入Cookie?
A:绝对不要将序列化数据直接存入Cookie,Cookie可被用户修改且长度有限,应使用Session(仅存储Session ID到Cookie),或加密后存储为base64:
$safe = base64_encode(openssl_encrypt(serialize($obj), 'AES-128-CBC', KEY, 0, IV));
Q3:PHP 8.x 对序列化做了什么改进?
A:PHP 8.0+ 强化了反序列化保护:
- 新增
unserialize()的options参数严格校验类名 __unserialize()优先于__wakeup()处理(8.1+)- 更严格的类型检查(如不允许序列化不存在的类)
Q4:如何排查反序列化漏洞?
A:使用静态分析工具(如PHPStan、Psalm)标注所有unserialize()调用;动态检测可安装PHPStan规则集或启用PHP的serialize_precision限制,推荐阅读OWASP PHP安全指南中反序列化章节。
Q5:序列化数据能跨语言解析吗?
A:不能,PHP序列化格式是PHP专有格式,无法被Java、Python直接解析,跨语言通信时,请使用JSON、XML或protobuf。
PHP序列化是强大的数据持久化工具,但安全风险极高,在现代开发中,建议:
- 业务逻辑使用JSON或
var_export(适合纯数组) - 必须使用时,对输入做签名验证
- 永远不要从URL参数、表单直接传入
unserialize()
如果想深入了解,推荐阅读《PHP安全之道》中的序列化章节,或查看PHP官方手册 serialize 与 unserialize 文档。