1. 漏洞原理
文件上传是web应用的必备功能之一,比如:上传头像来显示个性化、上传附件来共享文件、上传脚本来更新网站等。如果服务器配置不当或者没有进行足够的过滤,web用户就可以上传任意文件,包括恶意脚本文件、病毒木马等。这就造成了文件上传漏洞。
文件上传漏洞的具体成因:
-
服务器配置不当会导致任意文件上传;
-
web应用开放了文件上传功能,并对上传的文件没有进行足够的限制和过滤;
-
web应用开放了文件上传功能,虽然在开发时加入过一定的过滤功能,但并不严格,可以被绕过。
文件上传漏洞的产生条件:
-
web服务器要开启文件上传功能,并且上传API(接口)对外开放,即web用户可以访问;
-
web用户对目标目录具有可写权限,甚至具有执行权限,一般情况下,web目录都具有执行权限;
-
我们上传的文件在服务器的系统环境里能够正常运行,即web容器能够解析我们上传的脚本,不论脚本以什么样的形式存在。
若无视以上条件,就认为服务器配置不当,相当于开启了PUT方法。
附:关于HTTP请求的PUT方法:
PUT方法是HTTP请求方法之一,允许向服务器直接写入文件
检测服务器是否开启了PUT方法:
可以通过telnet向远程服务器发送一个OPTIONS方法的HTTP请求包
telnet 172.16.132.161 80
————————————
OPTIONS / HTTP/1.1
HOST:172.16.132.161
———————————
Apache如何开启PUT方法:
第一步:在httpd.conf文件,开启以下两个模块(即把注释#去掉)
LoadModule dav_module modules/mod_dav.so
LoadModule dav_fs_module modules/mod_dav_fs.so
第二步:在httpd.conf文件,添加一个Dav On
DocumentRoot “C:/xampp/htdocs” <Directory “C:/xampp/htdocs”>
Require all granted Dav On AllowOverride None Options All Order allow,deny Allow from all
</Directory>
第三步:开启webdav锁(文件锁)
首先新建文件DavLock,然后在httpd.conf中新增一行如下(路径写对应的DavLock文件所在路径)
DavLockDB D:phpStudyPHPTutorialWWW
2. 漏洞危害
文件上传漏洞最直接的威胁就是能够被上传任意文件,包括恶意脚本、病毒木马等。如果web服务器所保存的上传文件所在的目录具有执行权限,那么就可以直接上传后门文件,导致网站沦陷。之后,如果攻击者再通过利用其他漏洞进行提权操作,拿到系统管理员权限,那么就会直接导致服务器沦陷为肉鸡,且同服务器下的其他网站无一幸免,均会被攻击者控制。
3. 漏洞利用
-
上传Web脚本程序,利用Web容器解释执行我们上传的恶意脚本。
-
上传Flash跨域策略文件crossdomain.xml,修改访问权限(其他策略文件利用方式类似)。
-
上传病毒、木马文件,诱骗用户和管理员下载执行;获取webshell,进而再提权获得最高控制权,
-
上传包含脚本的图片,某些浏览器的低级版本会执行该脚本,用于钓鱼和欺诈。
等等利用方式,想象空间很大。
4. 防御绕过方法
文件上传的方法大都是通过一些黑白名单策略来对上传文件进行限制,我们只要想办法绕过黑白名单策略的限制即可成功上传我们的恶意文件。
黑白名单策略:
黑名单策略:拦截在名单内的任何进程或请求,即名单里都是坏蛋
白名单策略:拦截不在名单内的任何进程或请求,即名单里都是好人
4.1 客户端检测文件后缀名
防御方法:
有些web应用的文件上传功能,仅在前端(客户端浏览器)用JS脚本做了检测,一般前端检测采用白名单策略,如检测文件后缀名等。
如以下是一个简单的JS代码实现的简单前端防御:
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
// indexOf函数的返回值==-1,代表所要检索的字符串在原文里不存在
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
绕过方法:
前端防御采用了JS来实现一个过滤功能,而JS的源文件是会被由服务器端发给客户端的,再由客户端浏览器来执行。
攻击者可以直接在客户端浏览器里查看元素,直接修改或删除页面里相应的JS防御代码段,即可绕过前端限制;或通过上传时先将后缀名改为白名单策略里的后缀名,然后再抓包将后缀名修改回来。
大概有以下两种方法:
-
方法一:先让我们上传的恶意文件的后缀名符合白名单策略,然后用Burp挂代理抓包,再将文件后缀名修改回来。
-
方法二:因为JS脚本的运行环境是浏览器,我们可以修改HTML、JS代码,使前端防御失效。
例如:我们发现表单里有一个事件监控,当提交时会触发执行一个check()函数。
而查找后发现,check()函数就是一段用JS写的实现前端限制功能的代码:
此时我们只需要,直接查看元素,修改HTML,删除表单里的事件触发onsubmit="return checkFile()"即可。
4.2 服务端检测文件MIME类型
防御方法:
对于文件上传漏洞,只从web前端(客户端浏览器)进行检测显然防护不足,那么服务器端的后端防护就显得特别重要了。一般服务器端检测采用黑名单策略,常见的检测内容如:检测MIME类型。
如以下是一个简单的PHP代码实现的简单后端防御:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
在上传文件时,服务端通过PHP代码检测上传文件的Content-Type字段来判断文件的MIME类型是否处于黑名单策略中,我梦可以通过Burpsuite作代理抓到HTTP请求包,使用或修改Content-Type字段来伪造文件的MIME类型。
4.3 服务端检测文件内容
防御方法:
除了检测上传文件的content-type字段里的文件MIME类型,为了进一步保证安全性,服务器还会检测文件内容。如:PHP中有一个函数getimagesize(),这个函数本意是检查图片的大小,但在检查之前,该函数会判断目标文件是否是一张图片,因此可以用该函数来检测文件的内容。
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 = "上传出错!";
}
}
}
绕过方法:我们可以上传符合黑白名单策略的文件,这样其文件内容就是看似合法的,但我们可以在文件内容里植入我们的恶意代码以绕过对文件内容的检测。例如可以通过制作图片马(详见木马专题)并上传,即可绕过一般的服务端文件内容检测。
4.4 服务端检测文件后缀名
防御方法:
除了客户端检测文件后缀名,还可以在服务端检测文件后缀名,这样就避免了被攻击者直接在客户端绕过的风险。服务端检测文件后缀名依然采用黑白名单策略。
绕过方法:
-
服务端使用黑名单策略:
方法一:可以通过将我们的恶意文件后缀名修改为一些冷门罕见的后缀名来尝试绕过,如:
php文件:.php可以修改为.php2、.php3、.php4、.php5、.phpt、.phtml
asp文件:.asp可以修改为.aspx、.ascx、.ashx、.asa、.cer
.jsp文件:.jsp可以修改为.jspx
方法二:还可以使用大小写混搭来尝试绕过。如:pHp、asPX等。
-
服务端使用白名单策略:
这种情况下,我们只能中规中矩地上传在白名单内的文件后缀名。但我们可以对上传的文件做一些捆绑或隐写操作来植入恶意代码,例如可以上传图片木马,然后再结合利用一些其他漏洞来使我们隐藏在上传文件中的恶意代码得以被执行.。
进阶绕过方法:
对于服务器检测文件后缀名的防御,除了以上基础的几种手段外,文件上传限制的绕过方法还有以下几种进阶方法:
-
利用00截断漏洞
-
利用.htaccess漏洞
-
利用文件解析漏洞
(详细内容在后文会介绍)