PHP反序列化漏洞底层原理在于将不可信序列化字符串还原为对象时,自动触发魔术方法(如__wakeup、__destruct),使其逻辑被用户控制的属性值驱动执行。其本质是数据被当作代码执行,而非函数缺陷;成立需同时满足:可控输入进入unserialize()、环境中存在危险类、能精准构造POP链。

PHP反序列化漏洞的底层原理,核心在于把数据当成了代码来执行。它不是函数本身有缺陷,而是当不可信的字符串被unserialize()还原成对象时,PHP会自动调用类中预定义的魔术方法——这些方法本用于对象生命周期管理,但一旦其逻辑可被用户控制的属性触发,就变成了攻击入口。
序列化字符串如何“活”过来
PHP序列化结果是一段结构明确的文本,比如:
O:4:"User":2:{s:4:"name";s:5:"Alice";s:3:"age";i:25;}
其中O表示对象,4是类名长度,"User"是类名,2是属性个数,后面是键值对。反序列化时,PHP不做来源校验,只按格式还原:分配对象内存、设置类名、填入属性值,并立即检查这个类有没有__wakeup()——如果有,就无条件调用。
立即学习“PHP免费学习笔记(深入)”;
关键点在于:还原过程不等于赋值完成,而是一次轻量级的对象实例化,附带生命周期钩子的自动激活。
魔术方法为何成为攻击跳板
以下魔术方法在反序列化过程中可能被自动触发,且执行上下文与正常业务逻辑一致:
-
__wakeup():反序列化刚结束、对象尚未返回给开发者时调用;常用于重置资源或恢复状态,若含system($this->cmd)且$cmd可控,即刻执行命令 -
__destruct():对象离开作用域或脚本结束前调用;攻击者常利用它做“收尾操作”,如写文件、发请求、删缓存 -
__toString():对象被拼接进字符串(如echo $obj)时触发;可作为POP链中间一环,把一次类型转换变成方法调用 -
__get()/__set():访问不存在/不可见属性时触发;适合绕过属性可见性限制,串联其他类的方法
这些方法本身合法,风险来自两点:一是它们自动运行,开发者无法跳过;二是它们往往直接使用对象属性,而这些属性值完全由序列化字符串决定。
漏洞成立的三个必要条件
一个可利用的反序列化漏洞,必须同时满足:
- 存在用户可控的输入点,且该输入被直接送入
unserialize()(如$_GET['data']、Cookie中的session值、缓存读取内容) - 环境中已加载至少一个具备危险逻辑的类(即Gadget),例如:
SoapClient可发起HTTP请求,Phar可触发文件操作,自定义类中含file_put_contents或call_user_func - 能构造出有效序列化字符串,使反序列化后对象的属性值精准导向Gadget中的危险路径(例如让
$obj->filename指向/var/www/shell.php)
缺少任一环节,链就断了。这也是为什么防御重点不在禁用unserialize,而在切断“可控输入→危险类→自动执行”的传导路径。
真实场景中的典型误用模式
很多漏洞并非出自恶意代码,而是开发习惯埋下的雷:
- 把前端传来的JSON先
json_decode再serialize存数据库,后端又用unserialize读取展示——攻击者可替换整个序列化字符串 - Session处理器配置不当,导致
session_start()自动反序列化恶意Cookie内容(尤其在session.serialize_handler=php_serialize时) - 第三方组件(如旧版ThinkPHP、WordPress插件)在处理缓存或配置时未过滤输入,将用户数据原样反序列化
- 用
unserialize(file_get_contents('config.dat'))加载配置,而配置文件权限失控或被覆盖
本质上,这是边界意识缺失:把“数据存储格式”误当作“安全容器”,忽略了序列化字符串自带执行语义。



















