upload-labs 通关笔记

Nov 24 2018 代码审计

upload-labs 通关笔记

前言

把上传的所有类型的漏洞都过一遍,然后做一个笔记,方便以后查看,在此也很感谢c0ny1大佬的平台。

运行环境

1
2
3
php版本:推荐5.2.17(其他版本可能会导致部分Pass无法突破)
php组件:php_gd2,php_exif(部分Pass需要开启这两个组件)
apache:以moudel方式连接

PS:为了节省时间,可下载Windows下集成环境,解压即可运行靶机环境。

Pass-01

我比较喜欢去看代码分析问题,所以仔细分析了程序的构造。

表单上传文件变量是upload_file

1
<input class="input_file" type="file" name="upload_file"/>

再来看下PHP代码,UPLOAD_PATH是在config.php文件里面的常量define("UPLOAD_PATH", "../upload/");,PHP的代码没有什么过滤的,直接就可以上传了。

但是在这个上传这里我弄了很久,因为Linux的一些机制接触比较少,用的环境是CentOS 7,PHP版本是5.4。

上传的时候一直失败,测试了$_FILES里面的参数都没什么问题,upload目录的权限也测试了,临时文件tmp也测试了,还是不行。

一直到下面的move_uploaded_file函数这里,百度了下已有人遇到同样的问题,解决的办法就关掉selinux输入:

0 ```
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

就OK了。

```php
<?php
include '../config.php';
include '../head.php';
include '../menu.php';

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {//判断UPLOAD_PATH上传目录是否存在
$temp_file = $_FILES['upload_file']['tmp_name'];//获取临时文件名
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];//上传目录+上传时的文件名
if (move_uploaded_file($temp_file, $img_path)){//把临时文件移到UPLOAD_PATH目录
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
?>

再往下看<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">

Get到javascript的知识点:

1
2
3
onsubmit="return false;" 将无论何时都阻止表单的提交
onsubmit="return check();" 是否提交表单取决于check()的返回值
onsubmit="check();" check()的返回值无影响

再往下看js的代码段,getElementsByNamename的值,getElementsById取的是id,取到的值如图所示

if (file == null || file == "")这段引来了一个思考,两个都是表示0为什么还有重复去判断呢?

1
2
null:代表声明了一个空对象,不是一个字符串,可以赋给任何对象,是没有地址。
"":代表声明了一个对象实例,这个对象实例的值是一个长度为0的空字符串,是有地址但是里面的内容是空的。

下面的判断文件上传的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<script type="text/javascript">
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));//eg. shell.php ext_name='php'
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name) == -1) {//如果ext_name不在allow_ext里面
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
</script>

由于是前端限制文件的后缀,可以有以下几种方法:

1.修改JS脚本

网上的方法是通过修改js的allow_txt的内容或者删掉onsubmit="return checkFile()禁止触发js函数

但是这个方法我今天测试不成功,删掉onsubmit="return checkFile()在火狐浏览器就能成功上传,所以在做渗透的时候,一个浏览器不行换一个浏览器试试,总会有新突破。

我本地写了一个上传的页面,测试成功!

1
2
3
<form enctype="multipart/form-data" method="post"  action="http://192.168.1.103/upload-labs/Pass-01/index.php">
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>

2.禁止浏览器运行JS

以Chomre为例子,进入设置->内容,把js禁止,然后去上传php成功!

3.burp抓包改包

先上传一个PHP图片马,然后抓包修改后缀就行了

Pass-02

第二关代码是判断MIME类型的,关于Content-Type的内容可以看https://www.cnblogs.com/52fhy/p/5436673.html

修改text/php 改成image/gifimage/pngimage/jpeg其中一个就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}

Pass-03

这一关我参考了s1ye的文章http://www.evi1.cn/2018/08/18/upload-labs%E9%80%9A%E5%85%B3%E6%8C%87%E5%8D%97/

他说的

PS:上传test.php. .适用于pass01-pass09。

实验中是不行的,因为common.php代码里面都把后面的.过滤了但是代码里面$file_ext取得文件后缀就不成立了,而且他发文章是2018年08月18日,但是GitHub上的是距今2018年11月,6个月前,那么就在5月份之后更新就没做修改了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#common.php
<?php
function deldot($s){
for($i = strlen($s)-1;$i>0;$i--){/
$c = substr($s,$i,1);//
if($i == strlen($s)-1 and $c != '.'){
return $s;
}

if($c != '.'){
return substr($s,0,$i+1);
}
}
}
?>

看下代码,过滤文件后面的..asp,.aspx,.php,.jsp这几个文件后缀。

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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$file_name = trim($_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
$file_ext = trim($file_ext); //收尾去空

if(!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

Apache解析漏洞

但是我们晓得Apache有解析漏洞,文件在/etc/mime.types。我在CetnOS上面安装的默认没有解析这几行:

1
2
3
4
5
6
application/x-httpd-php          phtml pht php
application/x-httpd-php-source phps
application/x-httpd-php3 php3
application/x-httpd-php3-preprocessed php3p
application/x-httpd-php4 php4
application/x-httpd-php5 php5

我添加一行phtml作为测试,修改后记得service httpd restart或者systemctl restart httpd.service

上传的时候把后缀改为phtml或者pht

成功解析

Pass-04

第四关代码显示有一个错误,运行的代码是没有改上传文件名的,但是显示的代码是用时间日期作为文件的命名的。用黑名单过滤了大部分的问题后缀,但是有一个Apache的.htaccess没有过滤。

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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".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");
$file_name = trim($_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
$file_ext = trim($file_ext); //收尾去空

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
//$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
$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 . '文件夹不存在,请手工创建!';
}
}

.htaccess(分布式配置文件)

.htaccess是Apache的又一特色。一般来说,配置文件的作用范围都是全局的,但Apache提供了一种很方便的、可作用于当前目录及其子目录的配置文件。

Apache的配置文件在/etc/httpd/conf下面的httpd.conf

要想使.htaccess文件生效,需要两个条件,一是在Apache的配置文件中写上:

1
AllowOverride

二是Apache要加载mod_Rewrite模块。加载该模块,需要在Apache的配置文件中写上:

1
LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so

新版的Apache配置文件包含在/etc/httpd/conf.modules.d里面

我们只需打开里面的00-base.conf修改就行了

重启服务器service httpd restart或者systemctl restart httpd.service

如果是Ubuntu则需要

1
sudo a2enmod rewrite

.htaccess文件可以配置很多事情,如是否开启站点的图片缓存、自定义错误页面、自定义默认文档、设置WWW域名重定向、设置网页重定向、设置图片防盗链和访问权限控制。但我们这里只关心.htaccess文件的一个作用——MIME类型修改。

有两种写法:

第一种

先上传.htaccess内容为:

1
AddType application/x-httpd-php jpg

意思是:网站的jpg后缀都作为php文件来执行

我们上传一个测试图片看看效果

第二种

可以把.htaccess的内容修改为

1
2
3
<FilesMatch "test">
SetHandler application/x-httpd-php
</FilesMatch>

或者

1
2
3
<FilesMatch "test.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

第一个会把test.xxx的文件解析为php文件,第二个只会把文件名为test.jpg的文件解析为php

我们尝试第二个看看效果

也是能成功解析的

Pass-05

这一关我又看到http://www.evi1.cn/2018/08/18/upload-labs%E9%80%9A%E5%85%B3%E6%8C%87%E5%8D%97/

的作者用.php. .的用法了,我仔细看了几遍common.php的代码,感觉没错啊,对不起我确实是一个杠精,我用Windows搭建了测试,测试还是失败的,问题出现在$img_path的变量

Linux

Windows

该函数简单明了,就是消掉末尾的点。也就是说只要构造”.php. .”即可绕过检测,当deldot函数检测到末尾的第一个点时将继续从后向前检测,当检测到空格时return xxx.php. (这里结尾时空格),接下来执行trim函数将去掉末尾的空格,最终文件名为 test.php. 由于windows特性,最后将变为test.php。

我们仔细分析下这段代码的作用吧,这里确实会返回xx.php.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function deldot($s){
for($i = strlen($s)-1;$i>0;$i--){
$c = substr($s,$i,1);
if($i == strlen($s)-1 and $c != '.'){
return $s;
}

if($c != '.'){
return substr($s,0,$i+1);
}
}
}
?>

但是到这里就不一样了,取后缀为空,作者估计strrchr有理解错误,误以为是取第一个.然后取后面的字符,

1
2
3
4

测试语句返回来的值是`.`

``` echo strrchr('1.php.','.');

官方的解释:

然后组成后缀

1
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;

那么我们上传后就会变成2018xxxx.

第五关的代码分析,没有用strtolower来转化大小写,所以用大小写来突破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
$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",".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 = 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)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

在Windows上面可以正常解析,Linux严格大小写所以看环境,下面是Windows的解析结果

Pass-06

第六关少了

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

所以在`1.php `这里后面加一个空格可以绕过

```php
$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",".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)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

在Linux上面测试失败,Linux的机制很严格;在Windows上面测试成功!

Pass-07

第七关还是黑名单,没有用deldot函数,就没有把.去掉

这一句取的是取两边空格的$file_name变量

$img_path = UPLOAD_PATH.'/'.$file_name;

利用windows特性,会自动去掉后缀名中最后的.,可在后缀名中加.绕过,Linux测试失败。

Pass-08

代码就不贴了,直接看重要的部分,和上面的区别就是少了一句替换和代码换成了取到后缀名最终的后缀名

1
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
1
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;

这样一看,::$DATA'是Windows的特性,具体可以看文章https://www.waitalone.cn/php-windows-upload.html

Pass-09

这关用了deldot上面有说这个函数的执行,如果上传的文件名为1.php.(这里是一个空格).那么得到文件名为$file_ext=.

但是这里改成

1
$img_path = UPLOAD_PATH.'/'.$file_name;

那么上传后的文件名就应该是1.php.

Linux是不能解析了,但Windows会自动把后面的.去掉

Pass-10

第十关突破点在这里

1
2

```$img_path = UPLOAD_PATH.'/'.$file_name;

str_ireplace可以去官网看说明http://php.net/manual/zh/function.str-ireplace.php

搜索有对应的关键词都会替换掉,但只搜索一次,替换没有限制。

可以利用双写绕过

Pass-11

%00截断

看代码已经用了白名单了,但是这里有一个$_GET['save_path']get来传递参数后面再加上后缀名

我们可以用%00截断来绕过

在url中%00表示ascll码中的0 ,而ascii中0作为特殊字符保留,表示字符串结束,所以当url中出现%00时就会认为读取已结束。

但是有环境限制:

php版本要小于5.3.4,5.3.4及以上已经修复该问题

magic_quotes_gpc需要为OFF状态

1
2
3
4
5
$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;

Pass-12

和11关区别就在于GETPOST,GET是可以把url自动转码的,但是POST不会,所以我们要做一些改动。

Pass-13

这关主要是利用了一个判断文件的函数

  1. fopen打开文件数据流
  2. fread读取2个字节
  3. unpack对二进制数据进行解包,C代表无符号字节型,后面的2代表个数,也可以用*代替
  4. 把两个chars连接起来再用intval转换为整数型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function getReailFileType($filename){
$file = fopen($filename, "rb");
$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;
}

绕过也比较简单,做一个图片马就行了,这关不要求解析成PHP

具体操作可以参考http://gv7.me/articles/2017/picture-trojan-horse-making-method/

上传后

Pass-14

getimagesize判断图片内置函数可以参考官方文档http://php.net/manual/zh/function.getimagesize.php

image_type_to_extension取文件后缀的内置函数http://php.net/manual/zh/function.image-type-to-extension.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)){
return $ext;
}else{
return false;
}
}else{
return false;
}
}

绕过方法可以用13关的图片马

Pass-15

这关需要开启php_exif模块

exif_imagetype 也是判断图片的类型的,具体可以看官方文档http://php.net/manual/zh/function.exif-imagetype.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
}

Pass-16

这关也是上传图片的,三段代差不多,取其中的一段来分析$target_path已经用了basename来限制你修改目录绕过的方法了。

$fileext以点为界,取点后面的字符作为后缀名。

变量$filetype获取的值取判断content-type是否符合条件

imagecreatefromjpeg判断是否为图片资源,具体可以看官方文档http://php.net/manual/zh/function.imagecreatefromjpeg.php

srand(time())看官方文档http://php.net/manual/zh/function.srand.php,和下面的[strval](http://www.php.net/manual/zh/function.strval.php)`(rand())` 相结合,随机数发生器的初始化,为了让上传的随机文件名不重复。

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
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);

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

}

imagecreatefromjpeg二次渲染它相当于是把原本属于图像数据的部分抓了出来,再用自己的API 或函数进行重新渲染在这个过程中非图像数据的部分直接就隔离开了。

绕过方法https://secgeek.net/bookfresh-vulnerability/ ,可以用里面的图片,具体怎么写exp在这里留个坑,改天再写个文章单独研究

Pass-17

这关是无论是什么文件先上传了再说,然后通过白名单检测后缀名,符合就rename改名,不符合就unlink删除文件。

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;

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 = '上传出错!';
}
}

但存在竞争条件,具体可以看文章http://homakov.blogspot.com/2014/11/hacking-file-uploaders-with-race.html

可以用burp来发包不断访问,先写一个php写入文件的test.php然后在访问这文件就可以生成另外一个shell了

Pass-18

这关还是同样的竞争条件,index.php调用了MyUpload这关上传类,使用了upload方法,执行的顺序是先上传然后再改名,但如何快速的多线程上传那就会出现bug,

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
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" );

}

绕过方法和上一关的一样,但是这关在上传之前用了白名单来检测,所以只能上传图片马,用Apache解析漏洞或者是文件包含漏洞。

Pass-19

这关$file_name = trim($_POST['save_name']);这里是决定上传后的文件名,有黑名单,而且这关要我们上传的是webshell

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
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 = trim($_POST['save_name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空

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 . '文件夹不存在,请手工创建!';
}
}
?>

我们可以用CVE-2015-2348https://www.cnblogs.com/cyjaysun/p/4390930.html 漏洞绕过

总结

用两周才写完这个笔记,主要是懒,不过加深了上传方面的知识还是收获到很多。

上面有什么错漏的欢迎留言。

upload-labs