文件上传漏洞
Published in:2023-06-29 | category: 前端 网络安全

文件上传漏洞

简介

文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令(webshell)的能力。文件上传在互联网应用中是很正常的需求,问题不是出现在需求上,而是在文件上传服务器后服务器如何处理、解析该文件,若服务器处理不恰当,则可能会造成比较严重的问题。

文件上传后导致的常见安全问题一般有:

  • 上传文件是 Web 脚本语言,服务器的 Web 容器解释并执行了用户上传的脚本,导致代码执行;
  • 上传文件是病毒、木马文件,黑客用以诱骗用户或者管理员下载执行;
  • 上传文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。

经典例子

Apache 文件解析问题

在 Apache 1.x、部分 2.x 中,对文件名的解析存在以下特性:Apache 对于文件名的解析是从后往前解析的,直到遇见一个 Apache 认识的文件类型为止。比如:

1
2
3
4
5
6
文件: PhpShell.php.rar.rar.rar // 上传至指定服务器根目录

内容如下:
<?php
phpinfo(); // 会输出php的描述信息
?>

因为 Apache 并不认识 rar 这个文件类型,所以会一直遍历到 php 后缀,然后认为这是一个 PHP 类型的文件。也就是说,当访问该资源时候,Apache 将会将其当成 php 文件执行:

1
http://127.0.0.1/PhpShell.php.rar.rar.rar

运行结果:

那么如何知道 Apache 认识哪些文件类型?通过 Apache 根目录下 conf 文件夹的 mime.types 文件便可以知道。

针对这种情况,在 apache conf/httpd.conf 文件中增加如下配置来禁止.php. .php3. .php4.等等这样的文件执行:

1
2
3
4
<Files ~ ".+.ph(p[3457]?|t|tml).">
Order Allow,Deny
Deny from al
</Files>

IIS 文件解析问题

IIS 6 存在这样一个漏洞:当文件名为 test.asp;xx.jpg 时,IIS 将会将其当成 test.asp 文件进行解析,;及其后面的 xx.jpg 被忽略,简单地说,文件名被截断了。

当服务器中存在 test.asp;123.jpg 的图片文件时,客户端通过如下 url 查看:

1
http://127.0.0.1/test.asp;123.jpg

正常情况下应该是返回一张图片,但由于 IIS 6 上面的这个特性(微软官方不承认这是个 bug),;及其后面的 123.jpg 被截去了,导致 IIS 6 直接将其当作.asp 文件来解析,从而造成了安全问题。比如:

1
2
3
4
文件: now.asp;123.jpg

内容如下:
<%=NOW()%> // asp获取当前时间函数,类似这种的图片称为图片木马

发起请求访问该文件:

1
http://127.0.0.1:8181/now.asp;123.jpg

返回结果:

除了上面这个漏洞外,IIS 6 还有个更加严重的问题。当服务器中建立如*.asa、*.asp 的文件夹时,其下的所有文件都会被当作 asp 脚本执行。如下:

1
2
3
4
文件: parsing.asp/now.jpg

内容如下:
<%=NOW()%> // asp获取当前时间函数

发起请求访问该文件:

1
http://127.0.0.1:8181/parsing.asp/now.jpg

返回结果:

不过,上述两个漏洞需要该文件确实存在于服务器本地存储中,若只是映射出来的 URL,是无法触发 bug 的。

WebDav

WebDav 大大扩展了 HTTP 协议中 GET、POST、HEAD 等功能,它所包含的 PUT 方法,允许用户上传文件到指定的路径下。但除了 PUT,它还支持 MOVE、COPY 等方法对服务器文件进行修改,这样就产生了如下的文本漏洞:通过 PUT 上传文件至指定路径,在结合 MOVE/COPY 方法修改该文件为脚本文件。例如:

1
2
3
4
5
6
7
8
9
文件名: 123.jpg // 通过PUT方法上传

内容如下:
<%=NOW()%> // asp获取当前时间函数

// MOVE或COPY方法改名
COPY 123.JPG HTTP/1.1
Host: 127.0.0.1:8181
Destination: http:127.0.0.1:8181/123.asp

然后再通过http:127.0.0.1:8181/123.asp访问,成功获取 webshell 执行脚本。此外,DELETE 方法的开启也可能导致文件被恶意删除,所以使用 WebDav 时需要多加注意。

PHP CGI 解析漏洞

2010 年 5 月,国内的安全组织 80sec 发布了一个 Nginx 的漏洞,指出在 Nginx 配置 fastcgi 使用 PHP 时,会存在文件类型解析问题:当请求 url 为http://www.test.com/1.jpg/1.php且 1.php 不存在但 1.jpg 存在时,会把 1.jpg 当作 php 类型文件进行解析执行。这就意味着攻击者可以上传合法的“图片木马”,然后在访问该”图片木马“时在 url 末尾加上“/*.php”),即可获得 webshell。

后来经人们深入研究发现,产生漏洞的根本原因并不是 Nginx,而是因为某些版本的 PHP CGI。在 PHP 的配置文件中有一个关键选项:cig.fi: x_pathinfo,这个选项在某些版本中是默认开启的,在开启时访问 URL,比如http://www.test.com/1.jpg/1.php,1.php 是不存在的文件,PHP 将会往前递归解析,于是造成了解析漏洞。这个往前递归的功能原本是想解决http://www.test.com/1.php/test这种 URL,能够正确地解析到 1.php 上。所以,在其他使用了该版本 PHP CGI 的 Web 容器中也存在上述漏洞,例如 IIS 7、IIS 7.5。

有人向 PHP 官方提供了第三方补丁试想修补这个 bug,但是官方不认为这是一个 bug,拒绝修改。官方给出的建议是将上述选项关闭,但是可想而知,对于不知情者仍然存在遭受损失的风险。

绕过检查的 N 种手段

常见的防御手段是进行文件类型检查,但由于疏忽或者检查不到位,容易被攻击者绕过。

单独地前端限制文件类型

1
2
3
4
5
6
7
8
9
10
11
12
function check() {
var filename = document.getElementById("file");
var str = filename.value.split(".");
var ext = str[str.length - 1];
if (ext == "jpg" || ext == "png" || ext == "jpeg" || ext == "gif") {
return true;
} else {
alert("这不是图片!");
return false;
}
return false;
}

仅通过表单上传前进行文件格式检查是否是图片,这显现是不可靠的。对于专业的黑客来说有 N 种手法绕过。

  • 正常上传图片格式,使用 BurpSuite 或其他工具截取数据包,并将数据包中文件扩展名更改回原来的,达到绕过的目的。例如正常上传 PHP 图片木马 1.jpg,截取数据包后将其改正为 1.php,然后通过 url 正常访问。
  • 直接通过开发者工具移除该事件函数。
  • 浏览器直接设置禁用 JavaScript。

这里使用的是白名单策略,相对来说并没有太多缺陷,但如果是使用黑名单策略问题就大了:通过.htaccess 规则文件绕过、通过文件后缀大小写绕过等等。

解释:.htaccess 文件(或者”分布式配置文件”),全称是 Hypertext Access(超文本入口)。提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。概述来说,.htaccess 文件是 Apache 服务器中的一个配置文件,它负责相关目录下的网页配置。通过 htaccess 文件,可以帮我们实现:网页 301 重定向、自定义 404 错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能,若安全检查通过黑名单策略进行,遗漏了此类型的文件时,攻击者就可以利用.htaccess 文件改变 web 容器的配置,制造破坏。

单独地后端限制文件类型

这里只讨论白名单策略安全检查。

1
2
3
4
5
6
7
$postfix = end(explode('.', $_POST['filename']));
if($postfix=='jpg'||$postfix=='png'||$postfix=='jpeg'||$postfix=='gif'){
//save the file and do something next
} else {
echo "invalid file type";
return;
}

上面这段 PHP 表示只允许接收保存 jpg、png、gif 三种图片格式,在 php<5.3.4 的版本中,有这样一个 bug:在部分字符串处理函数中,0x00 被认为是终止符。假设上述代码拦截的请求中,filename 为 1.jpg%00.php,在 explode 中则可能会被当作 1.jpg 处理,然后得到的后缀就是 jpg 了,成功绕过检查,但存储的确实是 php 格式文件。

防御手段

文件上传的目录设置为不可执行

只要 Web 容器无法解析该目录下的文件,即使攻击者上传了脚本文件,服务器本身也不会受到影响,因此此点至关重要。在实际应用中,很多大型网站的上传应用,文件上传后会放到独立的存储上,做静态文件处理,一方面方便使用缓存加速,降低性能损耗;另一方面也杜绝了脚本执行的可能。

windows 可通过右键->属性打开下面面板进行修改文件夹权限:

linux 则可以通过命令进行修改:

1
chmod 777 /use/www/WebSecurity

判断文件类型

判断文件类型也能在一定程度上避免上传漏洞,前后端双重校验效果最佳。

前端检验:

1
2
3
4
5
6
7
8
9
10
11
12
function check() {
var filename = document.getElementById("file");
var str = filename.value.split(".");
var ext = str[str.length - 1];
if (ext == "jpg" || ext == "png" || ext == "jpeg" || ext == "gif") {
return true;
} else {
alert("这不是图片!");
return false;
}
return false;
}

后端检验(PHP):

1
2
3
4
5
6
7
$postfix = end(explode('.', $_POST['filename']));
if($postfix=='jpg'||$postfix=='png'||$postfix=='jpeg'||$postfix=='gif'){
//save the file and do something next
} else {
echo "invalid file type";
return;
}

前端在表单上传之前检查文件类型,若不符合则不允许提交。单靠表单检查类型是不安全的,因为前端的代码时完全暴露并且允许在开发者工具中进行修改,比如直接去掉这个事件绑定,对于专业人员来说完全起不了作用,所以需要结合后台进行类型限制。无论是后台还是前端,统一使用白名单策略,指定允许通过的文件类型,不在白名单外的全都视为非法类型不允许通过,相对黑名单策略更加安全,因为黑名单总有遗漏的文件类型。

此外,还可以检查 HTTP Header 中的 Content-Type,HTTP 协议规定了上传资源的时候在 Header 中加上一项文件的 MIMETYPE,来识别文件类型,这个动作是由浏览器完成的,服务端可以检查此类型。不过这仍然是不安全的,因为 HTTP header 可以被发出者或者中间人任意的修改,不过聊胜于无。

使用随机数改写文件名和文件路径

文件上传如果要执行代码,则需要用户能够访问到这个文件。在某些环境中,用户能上传,但不能访问。如果应用使用随机数改写了文件名和路径,将极大地增加攻击的成本。与此同时,像 shell.php.rar.rar 这种文件,将因为文件名被改写而无法成功实施攻击。

单独设置文件服务器的域名

由于浏览器同源策略的关系,一系列客户端攻击将失效,比如上传包含 JavaScript 的 XSS 利用等问题将得到解决。但能否如此设置,还需要看具体的业务环境。

总结

文件上传是一个常见的业务需求,但因其与服务器直接交互,所带来的安全问题也是相对严重的,也是黑客的重点关注对象,为此需要安全人员做足充分的安全检查,才能保证服务器安全。

Prev:
输入法
Next:
跨站脚本攻击