前言

上周末的比赛有一道题要用phar反序列化,结果当时不会。。。还是学的太少了。

phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作

phar文件结构

phar本质上其实就是种压缩文件,它主要由四部分构成

1.Phar file stub (头部标识)

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

2. Phar manifest file entry definition (内容清单)

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

image-20220428111105321

3. the file contents

被压缩的文件内容

4. [optional] a signature for verifying Phar integrity (phar file format only)

文件签名,在文件末尾,格式:

image-20220428112035499

demo测试

根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作。

注意:要将php.ini中的 phar.readonly选项设置为Off,否则无法生成phar文件。

image-20220428112853834

phar_generate.php :

<?php
    class TestObject {
    }

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

运行,打开查看生成的 phar.phar 文件

可以明显的看到meta-data是以序列化的形式存储的:

image-20220428113241876

既然有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:

图片来自Seebug

具体的底层原理这里不再探究

本地测试下如何利用 phar 反序列化

test_11.php :

<?php 
    class TestObject {
        public function __destruct() {
            echo '_destruct() called!';
        }
    }

    $filename = 'phar://phar.phar/test.txt';
    file_get_contents($filename); 
	//unlink($filename);

运行结果:

image-20220428114333466

当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作。

phar反序列化通常与文件上传共同作用。

将phar伪造成其他格式的文件

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

<?php
    class TestObject {
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();

采用这种方法可以绕过很大一部分上传检测。

利用条件

  1. phar文件要能够上传到服务器端。
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且:/phar等特殊字符没有被过滤。

参考链接

https://paper.seebug.org/680/#22-demo