upload-labs1

文件上传漏洞原理

成因

1.服务器配置不当会导致任意文件上传
2.web应用开放了文件上传的功能,并对上传的文件没有进行足够的限制和过滤
3.web应用开放了文件上传功能,虽然在开发时加入了一定的过滤功能,但并不严格,可以被绕过
4.上传文件时如果服务端代码未对客户端上传的文件进行严格的验证和过滤,就容易造成可以上传任意文件的情况,包括上传脚本文件(asp、aspx、php、jsp等格式的文件)。
https://blog.csdn.net/qq_36119192/article/details/84593150
上面这个是关于那几个后缀的文件的说明

产生条件

1.web服务器要开启文件上传功能,并且上传API(接口)对外开放,即web用户可以访问;
2.web用户对目标目录具有可写权限,甚至具有执行权限,一般情况下,web目录都具有执行权限;
3.我们上传的文件在服务器的系统环境里能够正常运行,即web容器能够解析我们上传的脚本,不论脚本以什么样的形式存在。

危害

恶意的脚本文件,又被称为webshell,webshell脚本称为一种网页后门,webshell脚本具有非常强大的功能,比如查看服务器目录、服务器中的文件、执行系统命令等
导图

img
https://xz.aliyun.com/t/2435?spm=5176.12901015.0.i12901015.74b5525csqU5p2

upload-labs1

步骤其实很简单,之前在学一句话木马的时候已经有学习过了
流程是:在txt文本中写下一句话代码——>将后缀改为jpg等图片格式的后缀——>然后上传,在上传的时候抓包,在burp里面将后缀修改为php,使其在后面能够被运行——>然后利用蚁剑等工具连接一句话木马即可。
img
img
img

img 将后缀改为php 并发送 然后在蚁剑中连接 img

好了 成功了
以上为方法一,我们可以在burp中修改后缀,从而达到注入的效果
这里就简述一下吧,因为之前打过一遍了忘记保存印象还挺深刻的:
方法二:
我们在index.php看到一个chekfile的过滤函数,然后再show_code里面看到一个JavaScript写的网页代码,我们可以知道,PHP中的代码在JavaScript中发挥作用,而在审查元素中我们可以看到JavaScript的函数调用
img
当我们把他删除掉后,也就不存在过滤这一说,直接上传即可了
方法三,直接禁用JavaScript函数调用
思考:PHP,JavaScript的联动操作

upload-labs2

第二关提示中说对MIME进行检查
什么是MIME?使用MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准,使用MIME类型可以设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。

在JSP页面中,contentType属性设置为:contentType=“text/html;charset=GBK”。

img

所以这里有两种方法:
1.**:我们还是上传jpg然后抓包更改后缀为php,因为这个时候MIME-Type已经变为image/jpeg避开过滤了
**2.**我们也可直接修改MIME-Type中的类型,将其修改为image/jpeg
img
img
依旧是可以成功的
接着看一下源代码
**move_uploaded_file()函数

将上传的文件移动到新的位置
语法:
move_uploaded_file(file,newloc)

file 必需。规定要移动的文件。
newloc 必需。规定文件的新位置。
newloc 必需。规定文件的新位置。

move_uploaded_file()函数漏洞

img

upload-labs3

黑名单

基于黑名单验证:只针对黑名单中没有的后缀名,文件才能上传成功。
从这里开始 我们就进入了黑名单的关卡:
看看代码吧
img
因为已经写过一遍我就大概说一下:
他是定义一组后缀名,然后对于传输过去的文件,经过删除文件名末尾的点,从文件的点开始向后截取,
以及将文件名全部转化成小写,最后移除旁边的空格。
其中strrchr()函数的作用就是搜素并窃取搜索点及以后的位置
他禁用了挺多的类型的后缀的但是,我们还可以使用其他后缀进行绕过
phtml,php3,php4,php5,pht

upload-labs 4

在这一关中 将很多php的拓展形式都过滤了
这个时候我们需要引进一个.htaccess文件
作用
htaccess文件时Apache服务器中的一个配置文件,负责相关目录下网页配置,可以帮我们实现网页301重定向,自定义404错误页面,改变文件扩展名等功能,其中.htaccess文件内容:SetHandler application/x-httpd-php设置当前目录所有文件都使用PHP解析,无论上传任何文件,只要符合php语言代码规范,就会被当做php文件执行。
https://blog.csdn.net/cmzhuang/article/details/53537591(深层理解博客)

上传.htaccess文件
文件内容如下:
SetHandler application/x-httpd-php
PS:这里需要将上传的文件不用前缀 即.htaccess
指定文件进行php的转化:
在写入.htaccess的时候,我们可以

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

写出这段代码,注意到开头”shuaige”意为含有文件内含有shuaige的文件都会被解析为php文件
这里要使用这个.htaccess要记得去apache里面配置一下

upload-labs5

在这关当中,我们注意到,他过滤空格和.只过滤了一次,所以我们可以通过构建123.php. .的形式进行绕过过滤

upload-labs6

在第六关中,发现他是没有对大小写进行过滤的
所以我们

在后缀做点手脚即可 然后抓包改回即可

upload-labs7

在第七关中没有去空,所以可以在后缀加个空格,然后抓包改回

upload-labs8

这关是在文件后加个. 在windows系统中,会自动把.给删除掉,所以可以先抓包加个.绕过过滤,然后文件上传到目录后,.会自动被删除

upload-labs9

在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名
然后我们点击上传图像后的文件,点击查看图像,删除后面的::$DATA即可
此时有个问题,为什么文件上传后,文件名变成随机数字了呢?
img
原因就在这
一开始猜测是有其他保护机制,查看源码 果然如此
但是修改后的文件名可以

img
使用burp 的go后 可以查找得到

upload-labs10

这一题和第五题的解题方法是一样的,但是注意到它的源码有些地方不太一样了
img
就是他的路径变成是直接加file.name,感觉还是上一个保护机制更强一些,虽然用处也不大哈哈哈

upload-labs11

首先来分析一下源码
其中有一个
str_ireplace()

img 它的作用是这样的:将file_name中符合deny_ext格式的全部替换成空 而在上面 它是将两边的空格全部去掉,所以我们其实可以使用**双写**的方式进行绕过: img 上传成功 img 并且可以成功显示 这就让人联想到sql绕过过滤了。

为什么要采取替换为空的方式?
因为如果为空后缀 是不符合格式,直接就上传失败了

upload-labs12

白名单

基于白名单验证:只针对白名单中有的后缀名,文件才能上传成功。

img

前面 都是对于文件后缀名的过滤,首先是使用substr()和strrpos()截取后缀进行白名单匹配,这里没什么能操作的空间,再往下看,看到一个在img_path中 file_ext是可控的!
这里就要介绍一下

截断上传

前提条件:php 版本 < 5.3.4且php的参数magic_quotes_gpc必须关闭

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

此时

path="upload/web/"
file="1.jpg"

此时如果我将路径改为,path=”upload/web/1.php%00”,那么拼接上去之后就变成”upload/web/1.php%001.jpg”,那么此时就相当于是上传的是1.php,而1.jpg的jpg被截断了,其实类似于mysql的注释符吧

注意下这里是get形式上传的

参考链接:https://bealright.github.io/2019/08/03/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E6%BC%8F%E6%B4%9E%E2%80%94%E2%80%94upload-labs(11-20)/

upload-labs13

这一题和上一题没多大区别,依旧参数可控,不过上一题是get形式,而这题是POST
那么区别是什么呢?
因为post不会像get对%00进行自动解码,所以需要用16进制进行解码
这里我们可以将burpsuit抓包以后将其发送至decoder,将php后的16进制码改为00即可
img
将20改为00即可
img
补充一张截图,这里的a只是为了说明空格的位置,空格其实也可以替换为其他的,因为我们后面会将这个位置的内容换成00,后面的内容均会被截断

upload-labs14

img 分析源码,fopen为什么后面使用的是rb? “r”,即文本读取模式,应该使用“rb”模式,也就是二进制读取模式打开文件。 发现他只读两个字节 unpack:从二进制字符串对数据进行解包(用什么打包就用什么解包),加@的原因就是让其怎么都可以执行不报错和一句话木马的那个一个意思 而这里的unpack的意思是 将$bin中的以二进制的形式解包输出到chars中。 而下面这个intval是转化十进制的意思,将本来输出的二进制再转化为十进制,接下来通过这个进行对比 **我一直在想如果是直接解析文件内容,都没解析后缀,怎么知道是什么文件,答案是这样的** 其实,文件对自身文件内容,有着自己的文件头标识,我们只需要文件转为16进制,然后看各个文件类型对文件头的定义,就可以知道文件的类型了,例如,jpeg图片格式的文件头(2byte)标识为:0xff, 0xd8,结尾(2byte)标识为:0xff,0xd9 而转化为二进制再转化为十进制也是一样的 https://blog.csdn.net/LiuBuZhuDeFanHua/article/details/82949144 这里是一些图片文件头以及解码

查阅了一些资料以后发现:
数据通信(通过二进制格式与其它语言通信)
数据加密(如果不告诉第三方你的打包方式,对方解包的难度就相对很大)
节省空间(比如比较大的数字按字符串储存会浪费很多空间,打包成二进制格式才需要4位<32位数字>

解题:
因为他只读文本的前两个字节来判断是否为图片形式,我们先上传图片,看看图片的前两个字节是什么
img
是这样的,那我们创建一个图片马试试

https://blog.csdn.net/ltysg0645/article/details/53996658
这是制作教程链接,这里采取的是使用cmd命令符窗口指令制作的方式
指令是:

copy 1.png/b+1.php/a 12.png

接下来上传文件
利用文件包含漏洞
在inlude.php界面,输入?file=路径+文件名
img

这样就是成功了

文件包含漏洞

https://www.freebuf.com/articles/web/182280.html
在这里已经讲得很清楚了,就不重复赘述了
这里用我的话讲就是:
1.在PHP中含有
require()
require_once()
include()
include_once() 这几个函数的时候,可以通过文件包含函数加载另一个文件中的PHP代码
然后我们再来分析一下源码
img

get一个file,如果这个file存在就输出file的内容(这边使用了include函数
而这里为什么图片里的php代码能被解析呢?
原因是,在混合代码html+php的框架下,

<?php ?>

文件内包含这个的内容会被解析并输出结果,但是如果不是PHP内容的则会直接原样输出。

拓展:
web服务器的pathinfo漏洞(文件解析漏洞)

文件解析漏洞,是指Web容器(Apache、Nginx、IIS等)在解析文件时将文件解析成脚本文件格式并得以执行而产生的漏洞。从而,黑客可以利用该漏洞实现非法文件的解析。(PS:全称是Internet信息服务(baiInternet Information Service,IIS)。是微软提供的一个zhiWeb服务程序,在开发中称之为Web容器)

web容器:web容器是一种服务程序,在服务器一个端口就有一个提供相应服务的程序,而这个程序就是处理从客户端发出的请求,如tomcat、apache、nginx等。(可以理解为编程语言提供环境)

img

upload-labs15

查看源码,发现他这次使用了一个
img
getimagesize获取文件后缀类型
img
——来自菜鸟教程
他的每个后缀都对应一个编号,png对应的就是3
这个时候就在想,如果他有多个后缀呢?
这个时候我们需要知道getimagesize他读取文件针对的是文件内容,也就是他会对目标文件内容转化而为16进制再去进行一个读取,而根据前面我们可以知道,不同文件类型他的16进制是不同的,在这题当中,我测试了.php.png .png.php的后缀,只要这个文件的本质是图片形式的,他最后上传后显示出来的后缀即为.png(图片)格式后缀的,我们看源码,他最后保存的路径,文件名即为

img

看上面这个图,他说是前面的十位随机数+.$res的后缀,而这个后缀,就是我们前面通过读取这个图片的十六进制内容所得到的。

所以这题,我们还是直接使用图片马的形式上传就行,因为图片马前面的内容刚好可以绕过过滤(还是图片)。
然后先点击查看图片,看看这个随机生成的文件名是什么img
知道以后就直接利用文件包含漏洞打开即可

PS:这里有个神奇的东西
img
在一堆乱码中居然有一个小星星,哇哦哈哈哈哈哈

upload-labs16

img 首先是exit_imagetupe()函数: 这个函数也是用来检查是否为图片类型的,函数读取一个图像的第一个字节并检查其签名,如果发现了恰当的签名则返回一个对应的常量,否则返回 FALSE。返回值和 getimagesize() 的值是一样的,但该函数要快得多。 所以说还是图片马的事情嘛~ 上传图片马 成功,这里就不放截图了

upload-labs17

还是依旧分析一下源代码

$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'];
//basename($...)获取文件名,如果是base($...,"php"),获取无后缀的文件名
$target_path=UPLOAD_PATH.'/'.basename($filename);

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

//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片,此处发挥功能的是imagecreatefromjpeg()函数,并且要让这个函数发挥作用,还需开启PHP组件中gd2
$im = imagecreatefromjpeg($target_path);

if($im == false){
$msg = "该文件不是jpg格式的图片!";
//这里unlike删除原文件!!
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
//这里unlike删除原文件!!
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
imagecreatefromgif():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像
imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像
imagecreatefrompng():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像
imagecreatefromwbmp():创建一块画布,并从 WBMP 文件或 URL 地址载入一副图像
imagecreatefromstring():创建一块画布,并从字符串中的图像流新建一副图像
move_uploaded_file() 函数将上传的文件移动到新位置。

有大神说:这里move_uploaded_file已经将图片马上传到服务器当中了,后面的二次渲染是不影响这个图片马的,所以说可以不管这个二次渲染,但是他后面有删除原文件的代码呀,最终上传到服务器的,也就只有二次渲染的文件,所以我也搞不懂为什么可以直接利用move_uploaded_file?原因就是他将第一个 @unlink($target_path); 给删除了

接下来我们就开始解题吧:
网上说gif的图比较好过 所以我们就照一张gif
img
对比我们的原图片,发现是这个位置的数据没有发生变化,所以我们就将木马插在这里,后面继续利用文件包含漏洞即可

upload-labs18

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

这一关需要代码审计,那就让我们康康代码将文件上传到服务器,然后进行判断,是否存在白名单后缀,如果存在的话,就保留下来,反之就使用unlink删除这个文件。
这题考的是条件竞争

条件竞争漏洞:

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

所以解题方向就是,在上传以后储存在服务器,到检查不通过的这段时间如果我们可以利用起来,就算后续他将文件删除了也无所谓,那么这段时间太短了,如何将其放大?那就利用burp的intruder,随便弄一个peyload让他在那边跑就行,然后我们在浏览器中直接访问该文件

img img 在观察文件再文件夹的状态时 ![img](https://raw.githubusercontent.com/Hwwg/myphoto/master/20210118122943.png) 便发现他一直处于删除恢复的状态,此时需要注意的是,burp的线程需要设置得大一些。

拓展

参数污染漏洞

HTTP参数污染,也叫HPP(HTTP Parameter Pollution)。简单地讲就是给一个参数赋上两个或两个以上的值,由于现行的HTTP标准没有提及在遇到多个输入值给相同的参数赋值时应该怎样处理,而且不同的网站后端做出的处理方式是不同的,从而造成解析错误。
即针对多个请求, 处理方式不同
HPP漏洞的产生原因一方面来自服务器处理机制的不同,另一方面来自开发人员后端检测逻辑的问题。HTTP 参数污染的风险实际上取决于后端所执行的操作,以及被污染的参数提交到了哪里。总体上HPP一般有两种利用场景:
1)逻辑漏洞,通常会造成IDOR,信息泄露,越权等漏洞;
2)作为其他漏洞的辅助,用于绕过漏洞的检测和Waf等。
这个漏洞其实之前在sql-labs的时候就有遇到过了。
参考文章https://mp.weixin.qq.com/s?__biz=MzI3MTQyNzQxMA==&mid=2247483892&idx=1&sn=bf1b7c8e6242a5b6c3ef2f6169df308b&chksm=eac0b3c9ddb73adfac67da0512b172dbe0a4eda1fab4108646e9e3d714f9a72b927d259ed307&scene=21#wechat_redirect

upload-labs19

这一关他让我们代码审计,发现

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

本关对文件后缀名做了白名单判断,然后会一步一步检查文件大小、文件是否存在等等,将文件上传后,对文件重新命名,同样存在条件竞争的漏洞。可以不断利用burp发送上传图片马的数据包,由于条件竞争,程序会出现来不及rename的问题,从而上传成功

upload-labs20

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()截断漏洞
思路:
上传一个jpg文件,内容为php代码,再上传过程中,由于最后保存的文件名我们可控,根据漏洞,将.jpg截断
即:

img img img

upload-labs21

在这一关中需要代码审计
可以看到后面

if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";

前面的过滤绕过似乎都没有多大意义,毕竟后面还有白名单,但是这个判断条件就很奇怪,如果不是数组类型,则执行此判断,反之就不执行了????那我们就将文件定名变成数组怎么弄呢?先往下看,他文件的命名也很有趣
1.reset()函数:指向数组中的第一个元素
2.count()返回数组中元素的个数
3.end()最后一个元素的值
4.explode() 函数使用一个字符串分割另一个字符串,并返回由字符串组成的数组。
接下来让我们逐个破解:
它先将file分割成一个个数组,假设为1.php
那么数组为file[0]=1 file[1]=php
为了绕过白名单限制,我们可以命名为1.php.jpg
接下来是.php最为后缀,1作为前缀变成新文件的文件名 这样好像可行??
试试,不行诶,
再来思考一下:
1.count()返回数组中元素的个数 而数组从0开始,说明3-1返回的还是后缀,也就是一开始的jpg,这也就说明了为什么最后重新拼接过后的文件名不是我们设想中的123.php
那么该如何解决这个麻烦呢?
看到move_uploaded_file,使用/.截断试试,或者00截断
但是这个时候我们需要让他构造完后变成123.php/.jpg才会起到截断作用,但是我们知道有.就会被分割成数组,这时又该如何呢?
再代码以上看

$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}

结合burpsuit
img
推测现在的file名字为这个save_name的值,如果我们直接 更改这个save_name为一个数组,那么file也会变成一个数组,这样就不会经过被分割的那个数组,处理恰当,也不会经过白名单,看来是不行,因为他选取的是file这个数组的最后一个元素并将其值直接赋值给ext,故这里的ext不可能再是一个数组,所以,数组最后的元素一定是jpg

img 但是还是不行?没有报错但却无事发生???说明上面是错的 再次调整,将0和1数组调换位置发现报错信息改变了,(这里说明数组要从大到小)变成是文件上传失败 这里尝试了几种组合方式得出: 加不加反斜杠都无所谓,只要为file[1]=0即可 必须先save_name[0]再save_name[2] 唯一不懂的点: 1.count不是返回数组数目吗,count算了一下有0 1 2 三个元素 那么3-1=2 那么返回的为什么是file[1]而不是2 截断上传漏洞原理:http://salt-neko.com/2019/10/27/%E6%88%AA%E6%96%AD%E4%B8%8A%E4%BC%A0%E6%BC%8F%E6%B4%9E/

小结

img
Author

vague huang

Posted on

2020-12-17

Updated on

2022-09-27

Licensed under

Comments