资讯动态

PHP反序列化漏洞攻防实战:从Pikachu靶场到代码审计防御

发布时间:2026/10/20 14:37:54 来源:尧图企业网站定制
提到反序列化漏洞做代码审计和漏洞挖掘的人基本都绕不开这一关。我当年就是在Pikachu靶场上把一条序列化payload从第一个字节拆到最后一个字节来回改了十几次才算真正把PHP反序列化这条链跑通。很多人对反序列化的理解停留在unserialize很危险这个层面但危险点具体在哪、如何构造一个合法可用的payload、拿到payload之后又能干些什么往往说不出个所以然。这篇文章就把我在Pikachu靶场上的反序列化漏洞攻防实战全过程完整记录下来从原理拆解、攻击构造到防御加固、问题排查一次性讲透适合正在学Web安全、准备代码审计入门或者想系统梳理PHP反序列化知识的朋友。1. Pikachu靶场与反序列化漏洞场景认知1.1 Pikachu靶场里反序列化模块的定位Pikachu是一套开源的PHP漏洞练习平台内置了大量常见的Web漏洞场景包括SQL注入、XSS、CSRF、SSRF、文件包含、RCE、XXE、反序列化等等。它跟其他综合性靶场不太一样的地方在于每个漏洞模块都尽量还原真实业务场景比如登录框、查询框、提交留言这些功能而不是简单粗暴地放一段漏洞代码让你看。这种设计让你在打靶的时候能感受到漏洞是如何生长在业务里的。反序列化漏洞模块在Pikachu里相对独立界面很简单一个输入框一个提交按钮提示你把一段序列化字符串提交上去。后端拿到这段字符串后直接执行unserialize然后把反序列化结果输出到页面。这个模块的设计意图非常明确就是让你在完全可控的环境里看清单次反序列化的完整闭环用户输入如何进入unserialize、对象的魔术方法如何被触发、输出如何落地。实话说Pikachu上的这个模块并不复杂甚至有点裸奔的感觉但它恰恰是理解真实反序列化漏洞最好的起点。真实环境里的反序列化攻击往往藏在复杂框架和业务逻辑之下一条POP链动辄十几个类嵌套调用初学者很难看清楚每一步到底发生了什么。而Pikachu把这个过程压缩成了最核心的三步你提交数据、系统反序列化、魔术方法执行。1.2 为什么用靶场来练反序列化漏洞在真实生产环境里测试反序列化漏洞最麻烦的问题不是找不到漏洞而是很难控制自己的操作边界。一旦payload构造不当可能直接把目标服务的进程打崩或者触发意外写入影响线上数据。靶场最大的价值就是让你放开手脚去试错把可能踩的坑先在本地全部踩一遍。还有一个很实际的原因反序列化漏洞的利用过程高度依赖目标环境里的类定义和PHP版本。同一个payload在PHP 5.6下能触发到PHP 8.2下可能无声无息。靶场环境可以随意指定PHP版本你可以放心地对比不同版本下的行为差异这在真实目标上基本做不到。打个比方学游泳的人不会直接跳进海里而是先在浅水区反复练换气熟悉了水感再去深水区。Pikachu靶场就是反序列化这块的浅水区。你先在靶场上把提交payload、观察触发、验证结果这套流程走到肌肉记忆后面再去看真实的框架反序列化链就不至于一头雾水。1.3 前置知识准备打这个靶场之前建议先确认自己具备以下基础不然可能会在细节上卡住能搭建并运行PHP环境。Pikachu靶场需要PHP和MySQL集成环境几分钟就能搞定。了解PHP类与对象的基本语法至少要能看懂类、属性、方法、魔术方法的概念。知道序列化与反序列化是什么。简单说序列化是把对象转成可存储或传输的字符串反序列化是把这个字符串还原成对象。会使用浏览器开发者工具或抓包工具查看和修改请求参数。这一步在验证数据流时非常关键。如果前面几项还有点模糊建议先花一个小时过一下PHP手册里的类与对象章节再回来继续看。2. PHP序列化与反序列化原理拆解2.1 序列化字符串的格式PHP里的serialize函数可以把一个对象转换成一个结构化的字符串这个字符串看起来有点怪但每个字符都有明确含义。写一个最简单的类来演示?php class S { public $test hello; } $obj new S(); echo serialize($obj);输出结果是O:1:S:1:{s:4:test;s:5:hello;}把这个字符串逐段拆开看结构相当清晰O:1:SO代表Object1表示类名的长度S就是类名。如果是数组前缀是a用于a:2:{...}这样的结构。:1:表示这个对象有1个属性。{s:4:test;}s代表字符串类型4是属性名的长度test是属性名。s:5:hello;属性值是长度为5的字符串hello。除了o和s序列化格式里还会出现这些类型标识标识类型示例i整数i:1024;d浮点数d:3.14;b布尔值b:1;或b:0;NNULLN;a数组a:1:{i:0;s:3:abc;}O对象O:1:S:1:{...}R引用R:2;表示引用对象中的第2个值不要小看这个格式很多人在手工构造payload时出错就是因为没搞清长度和类型标识。比如类名长度写错了PHP直接报Class not found属性个数写错了反序列化结果就多出意外的属性导致魔术方法拿到错误数据。2.2 魔术方法是触发点反序列化漏洞能成立靠的不是unserialize函数本身而是反序列化过程中自动触发的一批魔术方法。这里说的自动触发意思是只要你调用unserializePHP就会在特定的时机调用这些方法不需要你显式调用。最常出现在漏洞里的魔术方法有这几个__construct对象创建时调用。__destruct对象销毁时调用。__toString对象被当作字符串使用时调用。__wakeupunserialize恢复对象时调用。__sleepserialize对象时调用。__call、__get、__set访问不存在的方法或属性时调用。反序列化攻击的核心手法就是构造一个特殊属性值的对象让这些魔术方法在执行时按照攻击者希望的方向走。举个例子假设目标环境里有这样一个类?php class S { public $cmd; public function __destruct() { eval($this-cmd); } }如果我能控制这个对象的cmd属性那么当对象被销毁时__destruct方法就会执行我传入的代码。构造一个序列化字符串O:1:S:1:{s:3:cmd;s:10:phpinfo();;}unserialize之后这个对象在生命周期结束时就会执行phpinfo()。这个例子是理想化的但完整展示了反序列化漏洞的触发链路序列化字符串承载攻击数据unserialize恢复对象魔术方法消费属性数据危险操作落地。2.3 反序列化攻击的核心思路理解了魔术方法就能明白反序列化攻击的完整逻辑了。攻击者的目标并不是让类被恢复而是让恢复出来的对象在某个时机执行危险动作。这个目标分三步达成第一步找到入口点。也就是目标应用在哪里把用户可控数据传给了unserialize。常见位置包括Cookie、接口请求头、JSON字段、消息队列消息体等。在Pikachu里入口就是一个文本框。第二步找到可利用的类。PHP反序列化时会根据字符串里的类名去目标环境里找对应的类定义。只要目标加载了这个类哪怕它只是某个业务模块里的普通类都可能被用于攻击。这也是为什么反序列化漏洞的利用代码经常依赖目标框架自带类库而不是攻击者自己上传的类。第三步构造POP链。单靠一个魔术方法往往不够真实场景里需要串联多个类的多个方法层层调用最终走到危险函数。这个概念有点类似栈溢出里的ROP链所以安全社区里叫它POP链或属性导向编程。用攻击者的视角去看反序列化漏洞真正的可怕之处在于攻击面不只是那个unserialize函数本身而是目标应用运行环境里所有已经加载的类。每多一个类就多一分被串联利用的可能。3. 攻防实战在Pikachu上完整打一遍3.1 环境准备与靶场启动Pikachu靶场的部署方式很成熟基本思路是准备一套PHPMySQL环境把源码放进Web根目录然后访问安装页面完成初始化。以本地测试为例大致步骤安装集成环境启动PHP和MySQL服务。把Pikachu源码解压到Web根目录比如htdocs下的pikachu文件夹。浏览器访问安装页面根据提示创建数据库、填写数据库账号密码。安装完成后进入靶场首页找到反序列化漏洞入口。版本方面建议选稳定版避免源码过旧导致PHP 7.4以上版本跑出兼容性报错。PHP版本建议用5.6或7.x跟靶场设计时的运行环境更接近。部署完成以后打开反序列化模块。页面就是一个输入框和提交按钮没有任何多余的元素这种极简设计反而让你能集中精力观察反序列化本身的行为。3.2 观察功能入口确认数据流靶场界面虽然简单但打靶不能只看表面。先用浏览器开发者工具看一下页面结构找到表单的字段名。通常会有一个类似data的参数承载你提交的序列化字符串。为了确认数据流先用抓包工具拦截一下提交过程。在输入框随便填一个字符串比如abc提交观察发出的请求格式。这里能确认两件事第一请求方法是POST还是GET第二序列化字符串是直接放在POST body里还是经过了URL编码。接下来从源码层面理解后端逻辑。Pikachu这类靶场通常会把Demo代码放在模块目录里打开后看到的处理逻辑一般是这样的?php class S{ public $test scriptalert(who am i)/script; } if(isset($_POST[data])){ $data $_POST[data]; $uns unserialize($data); echo $uns; }注意这段代码的简化逻辑用户提交的data直接进入unserializeunserialize的返回值被echo。这里有两个关键点值得停下来想一想。第一个关键点是类S里定义了$test属性初始值是一段XSS payload。这意味着正常访问页面时对象被创建并回显页面会直接弹窗。第二个关键点是$test属性值被序列化后是可以被攻击者完全替换的。真正危险的是如果你能找到环境里另一个类的__destruct或__toString方法里存在危险操作比如文件删除、代码执行那么替换属性值就不再只是触发XSS了而是直接升级为RCE。3.3 一步一步构造序列化payload理解了后端逻辑下一步就是构造payload。有两种方式手工构造和脚本生成。我的建议是先用脚本生成一个标准payload再手工调整属性值这样能最大限度减少格式错误。先创建一个本地的PHP文件比如payload.php?php class S { public $test hello; } $obj new S(); echo serialize($obj); ?浏览器访问这个文件输出O:1:S:1:{s:4:test;s:5:hello;}把这个字符串复制到靶场输入框提交如果页面回显hello说明整条链路已经通了。这时候你已经完成了第一次完整的反序列化利用接下来要做的是把属性值替换成真正有攻击性的内容。把test属性改成一段XSS payload?php class S { public $test scriptalert(document.cookie)/script; } echo serialize(new S());生成后的字符串看起来比之前长很多因为属性值长度需要重新计算。这里就是手工构造最容易翻车的地方一旦属性值里包含引号、花括号长度稍有出入整个序列化字符串就失效了。所以最稳妥的流程永远是先写PHP代码再让serialize函数帮你生成字符串。还有一种常见场景是属性值里面包含单引号比如scriptalert(xss)/script在序列化字符串里这些引号会被原样输出粘贴到输入框时要注意不要被表单自身的编码规则吃掉。3.4 提交payload完成漏洞利用把生成的payload粘贴到靶场输入框点击提交。如果一切正常页面会执行JavaScript弹窗或者直接把script标签里的内容原样输出到页面上。到这里一次反序列化漏洞的完整利用就算闭环了。从这个过程能看出来反序列化漏洞利用的本质就是把一段精心构造的对象字符串喂给unserialize让目标应用帮你重建一个带毒的对象再借由魔术方法触发后续行为。这里还要多说一句如果页面输出被HTML实体编码你可能看不到弹窗只能看到script标签变成了一串转义字符。遇到这种情况先别急着认为利用失败去检查输出点是不是被做了处理。在Pikachu这种演示环境里通常不会做太多限制但如果实战中遇到编码问题就需要把payload调整成能被当前过滤逻辑放过的形式或者寻找环境里其他输出点。3.5 从poc到真实利用原生类与扩展思路在Pikachu靶场上验证完基础POC之后再往深走一步聊聊真实环境里的利用思路。你不可能要求每个反序列化入口旁边都刚好躺着一个带eval的类更多时候需要借助目标环境自身的类库来构造利用链。第一个方向是PHP原生类。PHP内置了一些类在特定条件下可以被反序列化利用。比如SoapClient类它的__call方法会发起HTTP请求如果能让这个类在反序列化后被调用不存在的方法就可能实现SSRF。又比如Error类在PHP 7环境下可以当作某个字符串处理链路里的切入点配合其他函数实现绕过。第二个方向是Phar反序列化。它的触发点不是unserialize函数而是phar://协议配合文件操作函数。只要目标存在文件读取、文件上传、文件删除中的一个操作并且你能控制操作的文件路径就可以构造一个Phar文件触发反序列化。这条路径隐蔽性很强代码审计时如果只搜索unserialize关键词很容易错过。第三个方向是框架自带类库的POP链。很多主流PHP框架的历史漏洞都出在反序列化上攻击链动辄十几个类。这类利用链的构造方法是先梳理框架里所有魔术方法再寻找能串联到敏感操作的调用链。这个方向对代码阅读能力要求较高建议从Pikachu这种基础模块起步逐步过渡到框架级分析。4. 防御视角源码加固与纵深防护4.1 最直接的修复禁止反序列化不可信数据防御反序列化漏洞最有效的手段不是加固某个函数而是从源头上不要让不可信数据进入unserialize。这个原则在安全社区被反复强调过永远不要反序列化用户可控的数据。为什么这个原则如此重要因为反序列化漏洞的根源不在某一个写法而在于对象重建机制给了攻击者影响应用内部对象结构的可能。与其在漏洞发生之后四处打补丁不如直接切断入口。在代码层面危险写法长这样$obj unserialize($_POST[data]);如果业务需求只是传一组数据完全可以改成JSON格式$arr json_decode($_POST[data], true);json_decode只还原数组和标量不会重建对象自然也不会触发任何魔术方法。很多团队在开发新接口时直接用JSON格式传参规避了整类问题就是这个道理。4.2 allowed_classes参数与json方案的取舍有些业务场景确实需要反序列化对象比如从Redis或数据库里读取缓存的PHP对象。这时候PHP 7提供了一个重要参数allowed_classes可以限制反序列化时允许创建的类。// 禁止创建任何类 unserialize($data, [allowed_classes false]); // 只允许白名单类 unserialize($data, [allowed_classes [MySafeClass]]); // 默认行为允许所有类危险 unserialize($data);说明一下这个参数的行为allowed_classes设为false时unserialize仍然会还原数据但遇到对象会把它转成__PHP_Incomplete_Class对象恶意类不会真正被创建魔术方法也不会被触发。这样至少能挡住一大波利用链。不过要注意两个坑。第一allowed_classes是PHP 7新增的低版本PHP 5.x不支持生产环境如果还跑老版本PHP这个方案直接不可用。第二白名单不是免死金牌。假如白名单允许的类自身就存在危险魔术方法攻击者依然可以在允许范围内构造攻击所以在白名单模式下还要逐个确认允许的类是否安全。4.3 运行时安全防护与代码审计建议纵深防御的思路是即使某个入口没有被代码审计发现也要有后续防线阻断攻击者。在数据源头增加签名校验是一个很实用的方案。服务端在序列化写缓存前对序列化字符串计算一个HMAC签名反序列化前先验签签名不通过直接拒绝处理。这个方案的设计思路是只有服务端自己产生的序列化数据才能通过校验攻击者从外部提交的任何数据都无法构造出合法签名。好处是业务改动不大坏处是密钥要严格保管一旦泄露就失去了防护意义。在WAF层面可以针对明显的序列化特征做封堵比如请求参数里出现O:\d:这类正则特征。但说实话这类规则很容易被编码和变形绕过WAF只能作为兜底不能作为唯一防线。代码审计时建议重点搜索以下关键词逐个确认输入是否可控unserializeyaml_parsejson_decode后是否有对象恢复逻辑phar://协议使用场景各种框架自带的序列化组件审计思路不是只找函数名而是要顺着数据流走一遍数据从哪来、有没有经过过滤、反序列化结果被谁消费、消费过程中有没有触发危险操作。只有把这条链路看清楚才算真正审到位。5. 实战问题排查与经验避坑5.1 常见问题速查表在打Pikachu靶场和后来做代码审计的过程中我整理了一些反序列化利用时最常见的卡点直接列成表格方便对照排查。现象原因解决方法提交payload后无任何回显属性名拼写错误、类名长度不符用serialize脚本生成标准payload不要手工硬写页面报Class not found目标环境里没有对应类定义确认类名和命名空间是否与目标环境一致魔术方法没有触发方法名拼写错误或方法不是public检查类定义确认魔术方法名称和访问级别页面输出被HTML编码输出点做了转义处理换一种能绕过的payload编码或寻找其他输出点反序列化成功但命令没执行危险函数被php.ini禁用查看disable_functions换用其他可利用的调用链PHP 8环境下行为异常部分魔术方法行为在PHP 8中有变化用PHP 7环境测试或者调整payload适应当前版本5.2 踩坑记录与个人体会第一次在Pikachu上构造payload我在属性个数上栽了跟头。当时手工写了一个多个属性的序列化字符串数字写错了结果unserialize出来的对象少了一个属性魔术方法拿到的值是空的折腾了半小时才意识到是格式问题。从那以后我给自己定了个规矩不管多简单先用脚本生成标准payload再手工改小字段。这个习惯后来在真实项目里帮我省了大量时间。还有一个印象很深的教训来自真实代码审计场景。某一次测试中反序列化的入口不在POST参数里而在Cookie头里。Cookie值被URL编码了一层我直接复制payload提交后端解出来是一堆乱码整个利用链卡在第一步。后来把payload先做一次URL编码再放进Cookie才顺利触发。这个细节教科书上很少写但实战中经常遇到。再看看学习路径的推荐。我建议按照这样的顺序进阶先把Pikachu上的基础反序列化模块打透做到能不看笔记独立构造payload然后研究PHP原生类在反序列化中的利用方式接着学习Phar反序列化因为它的触发面更广最后尝试复盘主流PHP框架的历史反序列化漏洞体会POP链的构造思路。每一步踩过的坑都值得记录下来形成自己的排查清单。最后分享一点个人体会反序列化漏洞并不难理解难的是把一条payload从能跑变成可复用、可审计。在Pikachu上验证成功的payload建议完整保留下来包括序列化字符串、PHP版本、类定义、触发结果这些信息在后续做代码审计时对照价值极高。安全这条路很多时候就是在这些看似枯燥的重复调试里积累出来的手感。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑