PHP 怎么PHP 序列化格式

wen PHP项目 3

PHP序列化格式深度解析:原理、安全风险与最佳实践

目录导读


什么是PHP序列化

PHP序列化(serialization)是指将PHP变量(对象、数组、字符串等)转换为可存储或传输的字符串格式的过程,与之对应的操作是反序列化(unserialization),即从字符串恢复为原始变量。

PHP 怎么PHP 序列化格式

核心作用

  • 持久化存储:将对象保存到文件或数据库
  • 跨请求传递:在会话(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

防御措施(按重要程度排序)

  1. 绝对不要反序列化不可信数据
    即使使用了allowed_classes参数,也无法完全规避魔术方法触发。
  2. 使用JSON替代序列化(推荐)
    json_encode/json_decode 不执行代码,且跨语言兼容。
  3. 启用签名验证
    在存入数据前使用hash_hmac签名,反序列化前验证:
    $data = serialize($obj);
    $signature = hash_hmac('sha256', $data, SECRET_KEY);
    // 存储时组合:$data . '|' . $signature
  4. 限制类白名单(PHP 7.0+)
    $obj = unserialize($input, ['allowed_classes' => ['MySafeClass']]);
  5. 禁用危险魔术方法
    在关键类中移除或重写__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官方手册 serializeunserialize 文档。

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