0%

Phar反序列化拓展攻击学习笔记

Phar

Phar归档文件最有特色的特点是可以方便地将多个文件分组为一个文件。这样,phar归档文件提供了一种将完整的PHP应用程序分发到单个文件中并从该文件运行它的方法,而无需将其提取到磁盘中。此外,PHP可以像在命令行上和从Web服务器上的任何其他文件一样轻松地执行phar存档。 Phar有点像PHP应用程序的拇指驱动器。(译文)

  • 简单理解就是他类似zip或者说类似jar,它将PHP文件打包成一个文件然后PHP可以在不解压的情况去访问这个包里面的php,并执行。

    Phar的结构

1. phar文件标识

<?php
Phar::mapPhar();
include 'phar://phar.phar/index.php';
__HALT_COMPILER();
?>

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

2. a manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

3. the file contents
被压缩文件的内容。

4. [optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾,格式如下:

Phar的使用方法

<?php
    class User{
        var $name;
        function __destruct(){
            echo "fangzhang";
        }
    }

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

得到的test.phar 内容如下:

00000000: 3c3f 7068 7020 5f5f 4841 4c54 5f43 4f4d  <?php __HALT_COM
00000010: 5049 4c45 5228 293b 203f 3e0d 0a5b 0000  PILER(); ?>..[..
00000020: 0001 0000 0011 0000 0001 0000 0000 0025  ...............%
00000030: 0000 004f 3a34 3a22 5573 6572 223a 313a  ...O:4:"User":1:
00000040: 7b73 3a34 3a22 6e61 6d65 223b 733a 343a  {s:4:"name";s:4:
00000050: 2274 6573 7422 3b7d 0800 0000 7465 7374  "test";}....test
00000060: 2e74 7874 0400 0000 46fc 6e5d 0400 0000  .txt....F.n]....
00000070: 0c7e 7fd8 b601 0000 0000 0000 7465 7374  .~..........test
00000080: 9d18 4c48 ba24 6ed6 a810 3690 2aac 034e  ..LH.$n...6.*..N
00000090: 6aee e818 0200 0000 4742 4d42            j.......GBMB

可以看到,有一部分是序列化之后的内容,就是我们在上一部分所说的manifest也就是meta-data

Phar反序列化原理

​ 使用Phar://伪协议去读取Phar文件时候,文件会被当做phar对象,meta-data部分会被反序列化。

测试Dome

<?php
    class User{
        var $name;
        function __destruct(){
            echo "test";
        }
    }

$file = "phar://test.phar";
@file_get_contents($file);
?>

漏洞利用

函数扩展

根据以上代码的测试可知,只要phar://协议解析文件的时候,就会造成序列化的问题,类似这样的函数不光有file_get_contents还有其他函数;

有大牛曾经总结过,所有文件操作的函数都可以触发这种序列化:

  • fileatime / filectime / filemtime
  • stat / fileinode / fileowner / filegroup / fileperms
  • file / file_get_contents / readfile / `fopen``
  • file_exists / is_dir / is_executable / is_file / is_link / is_readable / is_writeable / is_writable
  • parse_ini_file
  • unlink
  • copy

还有大牛深入的分析过这些函数的原理,并且加以扩展:

  • exif_thumbnail
  • exif_imagetype
  • imageloadfont
  • imagecreatefrom***
  • hash_hmac_file
  • hash_file
  • hash_update_file
  • md5_file
  • sha1_file
  • get_meta_tags
  • get_headers
  • getimagesize
  • getimagesizefromstring

几乎所有和IO有关的函数都涉及到了

绕过图片头检查

<?php
class TestObject {}

$jpeg_header_size = 
"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\x00\xff\xfe\x00\x13".
"\x43\x72\x65\x61\x74\x65\x64\x20\x77\x69\x74\x68\x20\x47\x49\x4d\x50\xff\xdb\x00\x43\x00\x03\x02".
"\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\x0a\x07\x07\x06\x08\x0c\x0a\x0c\x0c\x0b\x0a\x0b\x0b\x0d\x0e\x12\x10\x0d\x0e\x11\x0e\x0b\x0b\x10\x16\x10\x11\x13\x14\x15\x15".
"\x15\x0c\x0f\x17\x18\x16\x14\x18\x12\x14\x15\x14\xff\xdb\x00\x43\x01\x03\x04\x04\x05\x04\x05\x09\x05\x05\x09\x14\x0d\x0b\x0d\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14".
"\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\xff\xc2\x00\x11\x08\x00\x0a\x00\x0a\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01".
"\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03".
"\x01\x00\x02\x10\x03\x10\x00\x00\x01\x95\x00\x07\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02\x1f\xff\xc4\x00\x14\x11".
"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20".
"\xff\xda\x00\x08\x01\x02\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x06\x3f\x02\x1f\xff\xc4\x00\x14\x10\x01".
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x21\x1f\xff\xda\x00\x0c\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\x92\x4f\xff\xc4\x00\x14\x11\x01\x00".
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda".
"\x00\x08\x01\x02\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x10\x1f\xff\xd9";

$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub($jpeg_header_size." __HALT_COMPILER(); ?>");
$o = new TestObject();
$phar->setMetadata($o);
$phar->stopBuffering();

限制使用phar://出现位置

$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';