前言
文件上传也是ctf中相当常见的一个考点,通过upload-labs的学习我们可以掌握各种不同的文件上传姿势。
关于文件上传
文件上传漏洞往往产生于服务器配置不当,导致任意文件的上传,或者在开放文件上传时没有对文件进行严格过滤。就会导致不法分子恶意写入一些webshell,窃取服务器内的文件,造成严重的危害。
webshell及一句话木马
webshell就是以asp、php、jsp或者cgi等网页文件形式存在的一种代码执行环境,主要用于网站管理、服务器管理、权限管理等操作。使用方法简单,只需上传一个代码文件,通过网址访问,便可进行很多日常操作,极大地方便了使用者对网站和服务器的管理。正因如此,也有小部分人将代码修改后当作后门程序使用,以达到控制网站服务器的目的。
而利用文件上传漏洞进行攻击时最常用的便是一句话木马,通过上传一句话木马文件配合菜刀、蚁剑等webshell连接工具等,获得目标服务器的读取权。
这篇博客详细记录了常用的一句话木马,之后做upload-labs靶场一般用到的是普通php一句话木马:
常用的一句话木马
对于一句话木马类型的文件上传,可以参考这篇博客学习:
Web安全-一句话木马
upload-labs
刷靶场建议先从黑盒开始,等到做不出来了就翻提示和源码做白盒,当然下面是直接从白盒的角度写的。如果文件名被更改,可以在Repeater中Go一下,并在返回中找到文件名。
Pass-01
首先在txt文件中写入一句话代码,但php后缀的文件被过滤了,因此我们改后缀格式为jpg并上传。
这题涉及到了JS检测,这题起过滤使用的是JS代码,而php代码在js中被读取,因此我们可以在f12审查元素中删去JS代码,或者直接禁用JS调用就可以直接上传php文件了。
不过一般我们使用另一个方法,使用burp suite抓包:
在蓝线位置我们发现我们上传的文件,在这里将jpg后缀改为php并forward,就成功上传了文件,我们可以在靶场根目录中的upload文件夹看到我们上传的这个文件。
由于本地靶场我们知道文件的绝对路径,因此直接蚁剑连接就可以了:
记得做完之后删除上传的文件。
Pass-02
这关涉及到的是MINE,即文件类型绕过,我们同样抓包查看:
我们可以发现这个Content-Type
,这里就对你上传的文件进行了一次类型检查,图中网页检查的类型为image/jpeg
,这是因为因为我们上传的本身就是jpg文件,因此和第一关一样更改后缀为php就可以了。
但还有另一种方法,如果我们直接上传php文件的话,我们会发现这次的Content-Type
变成了application/octet-stream
,导致检查不通过,因此我们将其更改为image/jpeg
也可以绕过检查。
部分源代码:
1 | if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) { |
MINE的原理是判断$_Files["upload_file"]["type"]
是不是图片格式(image/gif、imge/jpeg、image\png),不是则不允许上传。$_Files["upload_file"]["type"]
的值是从请求数据包中Content-Type中获取。
Pass-03
提示告诉我们这题过滤了.asp|.aspx|.php|.jsp这四个后缀,不过没了这四个后缀,我们还有很多其他替代品,例如:
- ASP:asa/cer/cdx
- ASPX:ashx/asmx/ascx
- PHP:php4/php5/phtml/php3/pht
- JSP:jspx/jspf
当然在使用这些后缀之前可能要先对apache进行配置,我们也可以fuzz测试其他没有被过滤的后缀。
这里也放一下源码:
1 | if (file_exists(UPLOAD_PATH)) { |
这串代码的意义是删去文件名末尾的点,并截取后缀,将后缀转换为小写,删去前面定义的字符串数组里的内容并收尾去空。
Pass-04
这关过滤的是真的狠,把我们前面讲的几个全部过滤了,当然我们也可以fuzz试试有没有漏网之鱼。在这关中,我们要使用到.htaccess文件来帮助我们过关。
这里截取了一段网络上关于.htaccess的介绍:
htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
其中,.htaccess文件中内容SetHandler application/x-httpd-php
用来设置当前目录所有文件都使用PHP解析,无论上传任何文件,只要符合php语言代码规范,就会被当做php文件执行。
首先我们要创建一个txt文件,在这个文件中写入SetHandler application/x-httpd-php
,接着更改后缀为.htaccess,但是由于windows的命名规则导致文件不能没有文件名,因此我们先随意取一个文件名。当然也可以使用FilesMatch
指定文件,示例如下:
1 | <FilesMatch "muma"> |
这意味着文件名字中带有muma的文件会被解析成php。在上传.htaccess文件时要使用burp suite将这个文件的文件名删去。接着只要上传我们的muma1.jpg,用蚁剑连接就可以了。
Pass-05
这一关连.htaccess都过滤了,但是我们查看源码发现,这题只过滤了一次.和空格,因此我们构建muma1.php. .
文件名就可以成功绕过了,进入upload发现文件依然是muma1.php
。
Pass-06
查看源码发现没有大小写转化,意味着这题可以进行大小写绕过,将文件后缀改为.phP或者.pHp等等都是可以的,当然更改.htaccess的大小写进行绕过同样可行。
Pass-07
查看源码发现没有对后缀名去空,那么我们上传一个muma1.php
文件,并使用burp suite抓包,将文件名改成muma1.php
,后缀名后添加空格即可绕过后端php脚本的检测,再上传到windows服务器上,会自动去除后缀名后的空格。
Pass-08
查看源码发现没有过滤后缀中的点,那么我们上传一个php文件后抓包将文件名后缀改成.php.
就可以了。windows系统特性会自动忽略最后一个点,添加点即可绕过后端的php检测,而且可以正常解析。
Pass-09
这一关没有过滤::$DATA
,我们在上传php文件后抓包,在文件后缀最后加上::$DATA
。在windows中::$DATA
会把::$DATA
之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA
之前的文件名,起到绕过后端检测的效果。
Pass-10
这关解法和Pass-05是一样的,muma1.php.
,而且还真没啥区别,严重怀疑是传重了。
Pass-11
看源码:
1 | if (file_exists(UPLOAD_PATH)) { |
这关起到过滤作用的是str_ireplace()函数,它会检测文件名中包含$deny_ext的字符并替换为空。在做sqli-labs时我们也遇到了类似的情况,同样的,使用双写绕过,将文件后缀改为.pphphp
这类的上传就可以了。这样,后缀在后端过滤掉php后,剩下的内容仍然为php。
而如果上传.pphhpp
这种是无法绕过成功的,因为没有连续的php,导致这个后缀没有被过滤而成功上传,但上传后的结果仍然是.pphhpp
,webshell工具无法连接这种后缀。
Pass-12
前面的题目均为黑名单绕过,意思是我们不能上传黑名单中的含有后缀的文件。而这一关往后几关是白名单绕过,意味着我们只能上传白名单中含有后缀的文件。
1 | $is_upload = false; |
我们发现$img_path直接拼接,而且GET的save_path可控,这里要运用到截断上传的原理。
url中,%00对应的ascii码值为0,而ascii中0作为特殊字符保留,表示字符串结束,所以当url中出现%00时就会认为读取已结束,而忽略后面上传的文件或图片,只上传截断前的文件或图片,这和sqli-labs的23关原理是一致的,相信刷完sqli-labs对截断这个操作已经不陌生了。
因此,我们首先上传一个muma1.jpg,接着抓包,修改GET内容为../upload/muma1.php%00
:
查看upload文件夹发现上传成功。
ps.php版本要小于5.3.4,magic_quotes_gpc需要为Off状态。
Pass-13
这关和上关思路是一致的,也是%00的截断,但是由于这题是POST注入,POST注入不会自动解码%00,因此我们需要修改16进制码来进行截断。
首先我们上传muma1.jpg并抓包,然后将抓包内容发送至Repeater中,在POST内容最后添加上muma1.php:
接着我们在Hex中找到对应的十六进制码(php的十六进制码为70 68 70),因此我们将在这串编码后的值更改为00,Go一次之后我们就可以在upload文件夹中发现muma1.php了:
Pass-14
这关涉及到了上传图片码,首先我们要了解什么是图片码。图片码,又名图片一句话。顾名思义,就是将一句话木马藏匿在图片之中,从而达到绕过检查的效果。我们来介绍两种制作图片码的方法:
cmd命令制作
将你的php木马文件和图片放在同一个目录之下,shift+右键在这个目录下打开cmd,输入以下命令:1
copy tupian.jpg /b + muma1.php /a shell.jpg
这里的tupian.jpg是我们的原始图片,muma1.php是一句话木马文件,最后生成的文件是shell.jpg。我们可以用010打开这个文件,发现木马已经被成功写入图片中:
同样的方法我们也可以制作png图片码和gif图片码。二进制软件应用(010、winhex等)
我们可以用010打开一张图片,直接在图片最后加上一句话木马,要注意不要破坏原文件的文件尾,导致文件上传时无法识别。
有了图片码,就可以直接进行文件上传了,但是由于菜刀和蚁剑等shell工具无法解析图片文件,因此我们要利用到文件包含漏洞。关于文件包含漏洞之前也写过了一篇博客,也可以参考下面这篇博客:
Web安全实战系列:文件包含漏洞
在upload文件夹中写入下面这个php文件:
1 | <?php |
这里我把文件命名为include.php
我们构建一个url读取图片:
1 | 127.0.0.1/upload-labs/upload/include.php?file=9220211125204748.gif |
读取成功,但是读取出来一段乱码,结果不明显,我们写入phpinfo()测试:
成功。
为了和第15关区别,我们进行源码分析:
1 | function getReailFileType($filename){ |
本关源码以二进制读取模式打开文件,并读取两个字节。而unpack的作用是解包,用什么打包就用什么解包,这里是将$bin中的以二进制的形式解包输出到chars中,接着使用intval()函数将值转换为十进制,并对比转换出来的值是否为对应文件的文件头值,最后将文件类型返回导$fileType中。因此我们只能上传jpg、png或gif格式的文件,否则检查不会通过。
Pass-15
这关直接上传上关制作的图片码,用同样的方法就可以解除题目。这两关的差别主要在于源码的检查方式不同,我们查看源码:
1 | function isImage($filename){ |
本关使用了getimagesize()函数获取到图片信息,接着使用image_type_to_extension()函数获取图片后缀,最后判断文件中是否存在.jpeg|.png|.gif文件(这个检查利用文件的十六进制码进行),如果有则成功通过检查,否则上传失败。因此我们制作的图片码可以绕过,因为图片码本身就是一张图片,前面的文件内容可以通过检查。
Pass-16
这关由于用到了exif_imagetype(),因此要开启php的php_exif模块。这关的检查就比较简单粗暴了,直接使用exif_imagetype()来判断图像的类型:函数读取一个图像的第一个字节并检查其签名,如果发现了恰当的签名则返回一个对应的值,如果不为.jpeg|.png|.gif文件则返回false阻止文件上传:
1 | function isImage($filename){ |
不过由于本质上还是检查.jpeg|.png|.gif文件,因此我们上传图片码并利用文件包含就可以顺利解题了。
Pass-17
这题提示我们对图片进行了二次渲染,这意味着我们之前写进图片中的一句话木马会被抹掉,我们上传一个图片码试试:
我们发现原本的一句话木马被抹去了,这就意味着写入失败,我们来看看源代码:
1 | $is_upload = false; |
这里二次渲染用到了几个和imagecreat有关的函数,例如imagecreatefromgif(),作用就是创建一个画布,并从GIF文件或URL地址中载入一副图像。渲染后的图像利用move_uploaded_file()将上传的文件移动到新位置下,最后用unlink()删除原文件。
关于这关的写入方法,由于gif文件在二次渲染之后会保留一段和渲染前相同的内容,而jpg与png则没有这段内容。因此我们选择gif文件,将木马写入渲染前后内容一致的部分中,利用文件包含漏洞解题就可以了。
Pass-18
这里涉及到了文件上传的竞争条件,2020年的黑盾杯就有这个考点。提示说这题需要代码审计,那么我们就分析代码:
1 | $is_upload = false; |
分析代码,这里程序用move_uploaded_file()对文件进行转存后读取后缀并进行对比,如果后缀不为.jpg|.png|.gif的话则使用unlink函数删除文件。而条件竞争漏洞就运用到了在多线程情况下,就有可能出现还没处理完,我们就访问了原文件,这样就会导致防护被绕过。
网站允许上传任意文件,然后检查上传文件是否包含webshell,如果包含删除该文件,或者网站允许上传任意文件,但是如果不是指定类型,那么使用unlink删除文件,在类似这两种情况下我们就可以考虑使用竞争条件漏洞解题。
那么我们要怎么利用这个时间差访问文件呢?答案就是爆破上传。使用burp suite营造很多人上传info.php(<?php phpinfo();?>
)的场景,同时我们用蚁剑连接,下面是具体流程:
首先我们上传文件并抓包,将抓包内容发送到Intruder中,随意构建一个payload爆破点,例如我选定GET中的action内容进行爆破,设置字典为1-9999的数字,设置10线程。
设置完之后进行攻击,在攻击的同时不断访问127.0.0.1/upload/info.php
,将phpinfo的页面刷新出来后在upload文件夹中就可以发现info.php也成功上传。
Pass-19
这关同样也是竞争条件漏洞,不过这关除了检测后缀,还检测了文件大小、文件名是否重复、目录是否可写等等,将文件上传后,对文件重新命名。本关上传图片码然后用文件包含伪协议读取即可。这告诉我们,只要我们访问的够快,程序就来不及重命名我们的文件。
我们可以写一个python脚本帮助我们读取文件:
1 | import requests |
跑到脚本出现OK后就成功了。
Pass-20
本关又一次用到了move_uploaded_file(),而且是由POST方式获取文件,因此可以使用13关的过关方式:%00截断,更改Hex中的十六进制码即可。
我们来看看本关的源码:
1 | $is_upload = false; |
这关检验的是黑名单,如果后缀与黑名单的内容一致则禁止保存,反之则以设定的保存名称作为文件名保存。介绍另一个方法,这里move_uploaded_file()除了%00截断之外,还会忽略掉文件末尾的/.
,这里我们可以利用这一点构建文件名:
发现info.php已经成功写入upload文件夹中。
Pass-21
这题是数组绕过与/.
绕过相结合,我们审计一遍代码:
1 | $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name']; |
我们注意到这段代码中有一个判断语句,如果$file不为数组则进行白名单检测,否则不检测。同时发现$file_name
经过reset($file) . '.' . $file[count($file) - 1];
处理,如果上传的是数组的话,会跳过$file = explode('.', strtolower($file));
,因此我们可以让$file
为数组:
首先绕过MINE,我们的目的是让文件成功命名为info.php/
,并通过move_uploaded_file()出去/.
。而命名的规则为$file_name = reset($file) . '.' . $file[count($file) - 1];
,因此我们要让reset($file)
返回info.php/,$file[count($file) - 1]
返回空值,即$file[0]
为smi1e.php/,$file[2]
为白名单中的jpg用于绕过过滤。这样就成功绕过了过滤。
但是这个看着真的很绕,也很难彻底理解,这里也就理解了个大概,具体原理还要继续深入研究。
.user.ini
在之前的关卡中,我们用到了.htaccess文件,但是这个文件的使用是有局限性的,只有在apache服务器环境下才可以使用。因此这里补充一个绕过方法:上传.user.ini文件。
只要服务器脚本语言为php,对应目录下有可用php文件,服务器使用CGI/FastCGI模式,就可以通过上传.user.ini绕过黑名单检验:
首先构建.user.ini文件,内容为:
1
auto_prepend_file=a.jpg //为了绕过过滤需要添加例如jpg的文件头
然后依次上传.uesr.ini和图片码,图片便会被解析成php文件
总结
系统地刷完了upload-labs,学习了文件上传的各种绕过姿势:
- MINE绕过
- 更改后缀(
'.php. .''.phP''.phtml''.php ''.php.'等等
) - 上传.htaccess或.user.ini文件
- 竞争条件
- 上传图片码
- %00截断
- /.截断
同时这个靶场也锻炼了代码审计能力,还是很有收获的。