Lemon's blog

文件上传漏洞——upload-labs(11-20)

Record my learning process of Upload-labs.

字数统计: 3.6k阅读时长: 15 min
2019/08/03 Share

前言:上次文件上传漏洞学习到第十关,这次继续学习

第十一关

分析源码
在这里插入图片描述
前面几行代码都是对后缀名进行限制,最重要的就是这一句代码

1
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

发现save_path用户是可控的,那么就利用%00截断来绕过

在此之前,先来了解一下截断上传的原理

在url中%00表示ascll码中的0 ,而ascii中0作为特殊字符保留,表示字符串结束,所以当url中出现%00时就会认为读取已结束,而忽略后面上传的文件或图片,只上传截断前的文件或图片

可以通过一个例子详解的了解一下

1
2
3
4
5
<% 
path="upload/web/"
file="1.jpg"
upfilename=path & file '最后的上传地址
%>

将路径改为path="upload/web/1.php%00",那么拼接之后,文件上传时就变成了
"upload/web/1.php%001.jpg",这时上传便将1.php上传进去,而1.jpg则被截断,我理解的就是相对于省略符号,将后面的内容给省略了,相当于MySQL注入语句中的#--+

下面就来做题,几乎是一模一样
在这里插入图片描述
上传成功
在这里插入图片描述

不过在上传时我才明白,原来截断了1 .jpg,但是1.jpg的内容是被1.php给继承过去了,我还一直认为是必须自己创建1.php, 里面包含内容那。。。,这次真的学习到了

方法:%00截断

附上大师傅们的博客
截断上传原理剖析
00截断原理分析

差点忘记了最重要的一条,要进行配置才能进行%00绕过

截断条件:

  1. php版本要小于5.3.4
  2. magic_quotes_gpc需要为Off状态

在这里插入图片描述
在这里插入图片描述
找到并修改即可

第十二关

分析源码
在这里插入图片描述
和十一关的代码基本相同,但是有一个点是不同的$_POST['save_path']save_path是通过post传进来的,只是传进来的方式不同,绕过的方法应该还是%00截断

但是这次不能直接抓包在后面加上%00,因为post不会像get一样对%00进行自动解码,所以得换另一种方法进行%00绕过,查看大师傅的做法才知道要在二进制中进行修改

在这里插入图片描述
70 68 70后面的2b改为00即可绕过

上传成功
在这里插入图片描述
方法:%00截断

第十三关

分析源码

那就先来做图片木马

输入命令

1
copy 1.jpg /b + 1.php /a shell.jpg

一句话木马确实已经插入
在这里插入图片描述

接下来就结合文件包含漏洞将图片中的php文件进行解析

upload-labs自带有文件包含漏洞
在这里插入图片描述

1
http://127.0.0.1/upload/include.php?file=upload/2020190806155326.jpg

在这里插入图片描述
解析成功

分析一下源码

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
function getReailFileType($filename){
$file = fopen($filename, "rb");//fopen() 函数打开文件或者 URL,"r" 只读方式打开,将文件指针指向文件头
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);

if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

可以看出做出限制的是这一段代码

1
$bin = fread($file, 2); //只读2字节

通过读文件的前2个字节判断文件类型,没有其他防护,所以可以上传图片马解析即可

第十四关

用十三关的图片马一样可以解析
在这里插入图片描述
分析一下源码

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
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);//getimagesize() 函数用于获取图像大小及相关信息
$ext = image_type_to_extension($info[2]);//image_type_to_extension — 根据指定的图像类型返回对应的后缀名。
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

进行限制的是这两段代码

1
2
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);

通过使用getimagesize()检查是否为图片文件,所以还是可以用第十三关的图片马绕过

第十五关

查看一下源码

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
function isImage($filename){
//需要开启php_exif模块
$image_type = exif_imagetype($filename);
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
default:
return false;
break;
}
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

做出限制的代码为:

1
$image_type = exif_imagetype($filename);

exif_imagetype函数获取图片类型,使用exif_imagetype()检查是否为图片文件
,所以还是可以用第十关的方法,这几关可以了解一些获取图片类型的函数

第十六关

如果还使用第十三关的图片马,发现其中的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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];

$target_path=UPLOAD_PATH.'/'.basename($filename);

// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);

//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
//imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像
if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);

if($im == false){
$msg = "该文件不是png格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}

先来了解一下代码中出现的一些函数

1
2
3
4
5
6
imagecreatefromgif():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像
imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像
imagecreatefrompng():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像
imagecreatefromwbmp():创建一块画布,并从 WBMP 文件或 URL 地址载入一副图像
imagecreatefromstring():创建一块画布,并从字符串中的图像流新建一副图像
move_uploaded_file() 函数将上传的文件移动到新位置。

上面的代码判断了文件后缀和content-type,而且后面的代码又考察了二次渲染
看了很多大师傅们的博客,都说这道题考查的是图像的二次渲染,但同时这段代码是存在逻辑漏洞的

1
if(move_uploaded_file($tmpname,$target_path))

move_uploaded_file()作if条件做判断时,如果返回true,上传的文件不管是否符号要求,到可以上传到服务器,不过这里主要考察的是二次渲染,接下来就用最简便的GIF图片来绕过二次渲染

上传GIF图片马
在这里插入图片描述
上传发现没有解析成功,将上传的图片下载下来,用winhex打开观察一下
在这里插入图片描述
经过二次渲染,发现文件名已经改变而且末尾的PHP代码已经被去除,既然后面插入不了那就对比一些上传前和上传后的图片,看看那些部分是没有改变的,将一句话插入其中
在这里插入图片描述
经过对比,这一段都是相同的,可以将代码插入其中,发现上传后的图片依然有PHP代码,上传成功
在这里插入图片描述
在这里插入图片描述
还有JPEG和PNG图片上传,但是都需要脚本,这里就暂时先不用这两种方法了,不过可以参考大师傅的博客,upload-labs之pass 16详细分析

方法图片对比,在winhex将代码插入到相同的部位进行绕过

第十七关

提示:需要代码审计!

那就来分析代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$is_upload = false;
$msg = null;

if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;
//in_array() 函数搜索数组中是否存在指定的值。
//rename() 函数重命名文件或目录。
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
1
2
3
4
5
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;

这一段代码先将文件上传到服务器,再判断后缀名,如果合法则保留下来,如果不合法,后面这段代码则起删除作用,删除在服务器的文件

1
2
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);

unlink() 函数删除文件

所以根据这个流程,可以通过条件竞争的方式在unlink()函数删除之前,访问上传文件,在此之前先来了解一下条件竞争

条件竞争漏洞是一种服务器端的漏洞,由于服务器端在处理不同用户的请求时是并发进行的,因此,如果并发处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生。

接下来就利用条件竞争删除文件的时间差绕过

在burp中不断发送上传webshell的数据包,然后不断在浏览器中访问,发现通过竞争可以访问到,设置多线程控制攻击请求的并发数,但是我的burp有问题,无法设置多线程,所以没有办法复现出来
在这里插入图片描述

方法:条件竞争

第十八关

随便上传图片马,发现
在这里插入图片描述
审计一下代码

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
//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload(UPLOAD_PATH);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}

//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );

......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){

$ret = $this->isUploadedFile();

if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->setDir( $dir );
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// if flag to check if the file exists is set to 1

if( $this->cls_file_exists == 1 ){

$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, we are ready to move the file to destination

$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// check if we need to rename the file

if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, everything worked as planned :)

return $this->resultUpload( "SUCCESS" );

}
......
......
......
};

先是 $ret = $this->move();,进行了一次文件保存,然后再 $ret = $this->renameFile();,进行了一次更改文件名,所以可以用条件竞争来进行绕过

方法:条件竞争

第十九关

发现和之前有些不一样
在这里插入图片描述
查看一下源码

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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
//pathinfo() 函数以数组的形式返回文件路径的信息。
if(!in_array($file_ext,$deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
}else{
$msg = '上传出错!';
}
}else{
$msg = '禁止保存为该类型文件!';
}

} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

查看大师傅的博客,
发现move_uploaded_file会忽略掉文件末尾的/.
而且文件名是从$_FILES['upload_file']['tmp_name']中获取的,所以用户是可以进行控制的,所以通过/. 来进行绕过

在这里插入图片描述
上传成功
在这里插入图片描述
除此之外那也可以用用move_uploaded_file函数的00截断漏洞绕过
在这里插入图片描述
在这里插入图片描述

第二十关

来源一道CTF题,需要审计代码,目前自己还审计不了,就参考大师傅们的,暂时先留到这,等水平提高了,再回头看这道题


总结:通过upload-labs又学习到了很多很好玩的知识,接下来学习又关密码学的知识,少年,继续加油!


参考博客:
Upload-labs 20关通关笔记
Upload-labs通关手册

CATALOG
  1. 1. 第十一关
  2. 2. 第十二关
  3. 3. 第十三关
  4. 4. 第十四关
  5. 5. 第十五关
  6. 6. 第十六关
  7. 7. 第十七关
  8. 8. 第十八关
  9. 9. 第十九关
  10. 10. 第二十关