最简单有效的防御手段是用 json_decode() 替代 unserialize(),前提是业务不依赖 php 特有对象行为;输入校验无效因 unserialize() 会自动触发魔术方法,校验滞后于执行;allowed_classes 必须显式传入非空数组才生效,空数组或 null 等同于不限制。

直接用 json_decode() 替代 unserialize() 是最简单、最有效的防御手段——前提是业务不依赖 PHP 特有的对象行为(如魔术方法、私有/保护属性语义、资源句柄等)。
为什么不能只靠输入校验防反序列化?
校验反序列化前的字符串格式(比如检查是否以 O: 开头)、过滤关键词(system(、__destruct)或做正则匹配,基本无效。攻击者可绕过:用合法类名 + 恶意属性值触发已存在的危险逻辑;或利用未被识别的 gadget 链路径;甚至在反序列化完成前就完成攻击(如 PHAR 协议触发)。
关键点在于:unserialize() 本身就会重建对象并自动调用魔术方法,校验永远滞后于执行。
PHP 7.4+ 的 allowed_classes 参数怎么设才真正生效?
必须显式传入数组,空数组 [] 或 null 等同于不限制,等于没开白名单。
- 仅需还原数组/标量:用
[false],彻底禁用所有类还原 - 确需还原对象:只列明业务真实用到的类,例如
['Cart', 'OrderItem'],禁止通配符('*')、正则、模糊匹配 - 若类名含命名空间,必须带完整命名空间,如
['App\Models\User'] - 会话自动反序列化不受此参数控制,需配合
session.serialize_handler = php_serialize并手动封装读取逻辑
哪些场景必须换掉 unserialize()?
凡涉及用户可控输入、跨服务传输、持久化存储的反序列化,都应优先重构:
- 会话数据:改用
json_encode()/json_decode()存储扁平数组,丢弃对象语义 - Redis 缓存对象:序列化前先
get_object_vars()转数组,或用__serialize()显式定义导出字段 - API 响应/请求体:统一走 JSON,服务端只解析为
array,不还原为对象 - 必须保留对象结构时:加签名,例如
$signed = hash_hmac('sha256', $data, $secret) . '|' . $data,反序列化前验证签名
还有两个容易被忽略的致命点
一是 phar:// 协议能绕过 allowed_classes:攻击者构造恶意 PHAR 文件,通过 file_get_contents('phar://xxx') 触发反序列化,必须在 php.ini 中设 phar.readonly = On;
二是合法类里的 __wakeup() 或 __destruct() 若含 system()、file_put_contents() 等操作,且参数来自可控制属性,白名单也救不了——得人工审计这些方法的实现逻辑,删或加固。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











