最有效的防御是直接禁用unserialize(),因输入过滤不可靠、allowed_classes必须显式指定、json可替代、必须保留时需白名单类+__wakeup校验+环境控制。

直接禁用 unserialize() 是最有效的防御手段,只要不反序列化用户输入,漏洞链就断了。任何试图“过滤后放行”的方案,在真实攻击场景中都已被反复证明不可靠。
为什么不能只靠输入过滤
攻击者能绕过几乎所有基于字符串的检测逻辑。比如把 O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";} 做 URL 编码、Base64 编码,甚至混入不可见字符,都能让正则或关键词匹配失效。更关键的是,合法的序列化结构本身不包含危险函数名——危险来自反序列化后对象的魔术方法执行,而非字符串里有没有 system 或 eval。
- 序列化字符串是语法结构,不是代码文本,过滤字符等于在修语法书的封面
- PHP 7.4+ 的
allowed_classes参数必须显式指定类名,true等同于没设防 - 黑名单禁止函数(如
exec、shell_exec)完全无效:POP 链可绕过函数调用,直接触发文件写入、SQL 查询、HTTP 请求等
替换 unserialize 的安全替代方案
绝大多数业务场景下,unserialize() 并非不可替代。JSON 是更轻量、更可控、更易审计的数据交换格式。
- 将
unserialize($_POST['data'])改为json_decode($_POST['data'], true),并配合严格类型校验(例如检查返回值是否为数组、键是否在白名单内) - 若需保留对象语义,用 DTO 类 +
json_decode(..., true)+ 手动赋值,避免自动调用魔术方法 - 缓存场景中,弃用
serialize()存储对象,改用 JSON + 类型注解 + 构造器重建,或直接存字段级数据 - Session 数据若依赖
php_serialize处理器,应确认未将用户输入直接塞进 session 变量;否则统一改为php_serialize+ 白名单字段过滤
必须保留 unserialize 时的强制加固点
极少数遗留系统或框架扩展无法立即移除 unserialize() 调用,此时必须叠加多层控制,缺一不可。
- 确保 PHP 版本 ≥ 7.4,且所有调用点都使用
unserialize($data, ['allowed_classes' => ['AppModelsSafeUser', 'AppModelsSafeConfig']]),禁止硬编码类名分散在各处 - 所有可能被反序列化的类,必须显式定义
__wakeup()方法,并校验属性名是否在预设白名单中(例如in_array($key, $this->safeProperties)) - 在
__destruct()开头加环境判断:if (!defined('YII_ENV_PROD')) { exit; },防止开发/测试环境残留调试逻辑被利用 - 禁止子类调用
parent::__wakeup(),尤其当父类是框架核心类(如yii\base\Model)时,其默认实现可能触发反射或动态方法调用
容易被忽略的底层风险点
真正出问题的地方往往不在你写的业务代码里,而在你没意识到它会参与反序列化的环节。
- Yii 框架的
cache组件若使用文件或数据库驱动,默认用serialize()存储,一旦缓存键可控(如用户传入的$_GET['id']),就可能形成反序列化入口 - 第三方 Composer 包中隐藏的
unserialize()调用(如某些旧版日志组件、队列适配器),需用grep -r "unserialize(" vendor/全局扫描 -
session_start()后读取$_SESSION中的值,如果该值曾由用户输入经serialize()写入,同样构成反序列化面
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











