文件上传漏洞的原理、绕过与防御
一、什么是文件上传漏洞?
在有上传功能的系统中,如果应用程序没有对用户上传的文件做严格的校验,那么可能会导致用户上传脚本文件,然后用户再通过访问这些文件的方式,来达到执行该脚本文件,从而控制服务器的目的。
可以看到,之所以会有文件上传漏洞,主要是由两个步骤处理不当导致的:
- 没有对上传的文件做校验,导致具有危害性的脚本被保存到了服务器上;
- 没有控制服务器对上传目录的解析和执行行为,导致误执行了危害性的脚本;
那么针对这两个步骤,通常有如下的防范措施。
二、对上传的文件做严格的校验
2.1 前端校验
前端页面在提交上传文件时,增加文件类型的校验,如果不是指定的文件类型,就拒绝提交。
这种方式只能算是防止正常用户的误操作,完全不能阻止有心的黑客上传危害性的脚本,编辑一下页面JS,或者使用BP工具就能绕过了。虽然没有大用处,但也不可或缺。
2.2 后端检查文件扩展名
在文件被上传到后端服务器的时候,先对文件的扩展名进行校验:
-
黑名单法
凡是在黑名单中的文件扩展名均不允许被上传,直接返回给前端错误信息;
-
白名单法
限制只有白名单中的文件扩展名才会被服务器接受;
通常情况下,如果白名单能满足需求,就尽量使用白名单,因为白名单更加的安全,而黑名单容易被意想不到的方法进行绕过。
比如,我们黑名单规则为,凡是上传文件后缀为php
、asp
、jsp
的都会被过滤,那么针对以下情况就会被绕过:
- 老版本的IIS服务器存在分号漏洞,即服务器会在解析上传文件的文件名时会把分号后面的内容丢弃,那么黑客构造一个文件名为
danger.asp;jpg
的危险脚本,就能绕过黑名单校验规则,且能被服务器解析为asp脚本,当访问该脚本的时候,就会执行文件的脚本程序; - 老版本的windows-server存在点号和空格漏洞,即会将文件名最后的点号或者空格自动丢弃,那么黑客构造一个文件名为
danger.asp.
或者danger.asp
(此处有空格)的文件脚本,就能绕过黑名单校验规则,且能被服务器解析为asp脚本,当访问该脚本的时候,就会执行文件的脚本程序; - 老版本的Nginx存在%00截断漏洞,即对文件名中%00之后的内容进行截断,比如黑客构造一个文件名为
danger.php%00.jpg
的危险脚本,当上传成功后,服务器在解析该文件的时候,会截断%00之后的内容,把文件当成danger.php
进行执行; - 老版本的apache服务器解析漏洞,即服务器在解析文件名的时候从右往左,遇到识别不了的后缀会自动跳过,读取可以识别的后缀,如果黑客构造一个文件名为
danger.asp.rar
或者danger.asp.gif
的危险脚本,就能绕过黑名单校验规则,且能被服务器解析为asp脚本,当访问该脚本的时候,就会执行文件的脚本程序;
如果我们使用的是白名单规则,即只允许上传文件后缀为jpg
、png
、gif
的文件,那么如上的情况一和二就可以避免,而且可以阻止其它我们暂时没有想到的绕过规则。
2.3 后端检查Content-Type
HTTP协议规定了上传资源的时候会在请求头Header中添加一项MIMETYPE,即Content-Type,用来标识上传资源内容的类型,这个动作一般由浏览器来完成,常见的MIMETYPE类型如下:
- text/plain,纯文本
- text/html,HTML文档
- text/javascript,JS代码
- application/x-www-urlencoded,POST方法提交的表单
- application/msword,MicrosoftWord文件
- application/变成语言,该种编程语言的代码
- application/pdf,PDF文档
- application/octet-stream,二进制数据
- application/xhtml+xml,XHTML文档
- image/gif+jpeg+png,各种类型的图片
- vedio/mpeg,动画
- multipart/form-data,POST提交时伴随文件上传的表单
后端在处理上传请求的时候,可以获取header种的Content-Type类型,来判断上传的文件是否符合要求。当然这种方式也很容易被绕过,使用BP工具,拦截浏览器请求,修改Content-Type类型就行了,虽然这种方法不保险,但是加上一层防护也还是必要的。
2.4 后端检查文件头内容
每一种特定类型的文件都会有自己固定的文件头,通常是用十六进制表示。
- JPG对应
FF D8 FF E0 00
- GIF对应
47 49 46 38 39 61
- PNG对应
89 50 4E 47
- ZIP对应
50 4B 03 04
- RAR对应
52 61 72 21
- DOC/XLS/PPT对应
D0 CF 11 E0 A1 B1 1A E1
后端可以检查文件头的前几位字节,就可以判断是否为合法的文件类型,但是这种方式也不是完全保险的,并不能替代检查文件后缀的方式,因为黑客完全可以构造一个脚本文件,其文件头伪装成正常的上传文件类型。
三、控制服务器对指定目录的行为
服务器对恶意脚本的执行是造成漏洞生效的直接原因。
我们一般会把用户上传的文件放在一个固定的目录,比如/upload下面,按照预想计划,当用户访问这个文件夹下面的文件时,应该发起下载逻辑,而不是把文件当成脚本去执行。因为,我们可以设置服务器对特定目录的行为来有效避免漏洞的发生。
四、其它预防手段
4.1 上传文件重命名
服务器对恶意脚本的执行是造成漏洞生效的直接原因。
所以我们还可以通过让黑客无法找到他上传的恶意脚本来避免这个问题。当文件上传到服务器之后,服务器可以对文件进行随机重命名,然后再保存到上传目录中。
4.2 隐藏上传文件路径
服务器对恶意脚本的执行是造成漏洞生效的直接原因。
所以,我们需要将文件上传的路径隐藏起来,使其不能被用户直接访问到。比如,我们将文件上传路径放在非项目内,或者放在别的服务器上。当用户上传文件的时候,通过程序将文件传输到对应的目录;当需要读取该文件的时候,也由程序以文件流的方式去对应的目录加载,如此,用户根本无法直接访问到上传的文件,更不能使其执行。
五、总结
我们需要清楚地意识到触发上传漏洞的两个必要条件:
- 用户上传了非法格式的文件;
- 用户通过访问的方式触发了服务器执行其上传的文件;
所以,我们首先需要对文件的上传做防护,但是所有的防护措施都是有漏洞的,不能保证100%安全,因此我们还需要防止服务器对上传的文件意外地执行。
通常情况下,自己参与的项目中的防护工作主要就是前端校验、后端白名单校验;然后配合隐藏文件上传路径的方式来防止文件上传漏洞的。