01
文件上传漏洞原理
在文件上传的功能处,若服务端脚本语言未对上传的文件进行严格验证和过滤,导致恶意用户上传恶意的脚本文件时,就有可能获取执行服务端命令的能力,这就是文件上传漏洞。
02
文件上传漏洞触发点
相册、头像上传、视频、照片分享、附件上传(论坛发帖、邮箱)、文件管理器等。
03
文件上传漏洞的形成条件
文件能够通过前端和后端过滤和文件处理
文件内容不会改变,能够被正确存储
存储位置在Web容器控制范围内
攻击者有权限访问存储目录并有权执行文件
只要破坏了其中的任一条件即可防止文件上传漏洞。
04
文件上传漏洞常见防御方法
0.无防护
以下代码实现了一个简单的文件上传功能
index.html
11<form action="/upload" method="post" enctype="multipart/form-data"> 22 <input type="file" name="uploadfile" > 33 <input type="submit"> 44</form>
上传文件的后端处理,基于Spring Boot
1 1package com.example.fileuploadvul.Controller; 2 2 3 3import org.springframework.stereotype.Controller; 4 4import org.springframework.web.bind.annotation.PostMapping; 5 5import org.springframework.web.bind.annotation.RequestParam; 6 6import org.springframework.web.multipart.MultipartFile; 7 7 8 8import java.io.File; 9 9import java.io.IOException; 1010 1111@Controller 1212public class UploadFile { 1313 @PostMapping("/upload") 1414 public String uploadFile(@RequestParam("uploadfile")MultipartFile file){ 1515//获取文件名 1616 String filename = file.getOriginalFilename(); 1717 //文件保存路径 1818 String path="F:\tmp\"; 1919 File outfile = new File(path + filename); 2020 try { 2121 file.transferTo(outfile); 2222 }catch (IOException e){ 2323 e.printStackTrace(); 2424 } 2525 return "success"; 2626 } 2727}
上面的代码没有任何的防护功能,存在文件上传漏洞。用户可以随意的上传任何文件、木马。
1.前端进行js校验
增加攻击成本,该种方式容易被绕过。
javascript示例:
1 1<form action="/upload" method="post" onsubmit="return judge()" enctype="multipart/form-data"> 2 2 <input type="file" name="uploadfile" id="checkfile" > 3 3 <input type="submit" value="提交"> 4 4 <p id="msg"></p> 5 5</form> 6 6<script type="text/javascript"> 7 7 8 8 function judge(){ 9 9 var file=document.getElementById("checkfile").value; 1010 if (file==null||file==""){ 1111 alert("请选择要上传的文件"); 1212 // location.reload(true); 1313 return false; 1414 } 1515 var isnext=false; 1616 //定义允许上传的文件类型 1717 var filetypes=[".jpg",".png"]; 1818 //提取上传文件的类型,其中这里需要注意用lastIndexOf而非indexOf,否则会被1.php.php绕过 1919 var fileend=file.substring(file.lastIndexOf(".")); 2020 //判断上传文件类型是否允许上传写法一 2121 for (var i=0;i<filetypes.length;i++){ 2222 if (filetypes[i]==fileend){ 2323 isnext=true; 2424 break; 2525 } 2626 } 2727 if (!isnext){ 2828 document.getElementById("msg").innerHTML="文件类型不允许"; 2929 // location.reload(true); 3030 return false; 3131 }else { 3232 return true; 3333 } 3434 //判断上传文件类型是否允许上传写法二 3535 if (fileend.indexOf(filetypes) == -1) { 3636 var errMsg = "该文件不允许上传"; 3737 alert(errMsg); 3838 return false; 3939 } 4040 } 4141</script>
绕过方法:
这里限制了只能上传.jpg .png文件,但是攻击者可以用burpsuite拦截修改包进行绕过。
如图,上传一个内容为php的jpg文件,然后在此处将filename修改为1.php,即可绕过前端js验证。
另外也可以利用插件禁用js后进行提交。
2.白名单/黑名单
2.1黑名单
2.1.1java示例:
1 1@Controller 2 2public class UploadFile { 3 3 @PostMapping("/upload") 4 4 public String uploadFile(@RequestParam("uploadfile")MultipartFile file, Model model){ 5 5 boolean flag=true; 6 6 String filename = file.getOriginalFilename(); 7 7 System.out.println(filename); 8 8 String suffix=filename.substring(filename.lastIndexOf(".")); 9 9 String[] blacklist={".jsp",".php",".exe",".dll","vxd","html"};//后缀名黑名单 1010 for (String s : blacklist) { 1111 if (suffix.equals(s)){ 1212 flag=false; 1313 break; 1414 } 1515 } 1616 if (flag){ 1717 String path="src\main\resources\static\upload"; 1818 File fileDir = new File(path); 1919 File outfile = new File(fileDir.getAbsolutePath()+File.separator + filename); 2020 try { 2121 file.transferTo(outfile); 2222 return "success"; 2323 }catch (IOException e){ 2424 e.printStackTrace(); 2525 } 2626 } 2727 else { 2828 model.addAttribute("msg","非法文件类型"); 2929 } 3030 return "index"; 3131 } 3232}
2.1.2php示例:
1 1$is_upload = false; 2 2$msg = null; 3 3if (isset($_POST['submit'])) { 4 4 if (file_exists(UPLOAD_PATH)) { 5 5 $deny_ext = array('.asp','.aspx','.php','.jsp'); 6 6 $file_name = trim($_FILES['upload_file']['name']); 7 7 $file_name = deldot($file_name);//删除文件名末尾的点 8 8 $file_ext = strrchr($file_name, '.'); 9 9 $file_ext = strtolower($file_ext); //转换为小写 1010 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA 1111 $file_ext = trim($file_ext); //收尾去空 1212 1313 if(!in_array($file_ext, $deny_ext)) { 1414 $temp_file = $_FILES['upload_file']['tmp_name']; 1515 $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; 1616 if (move_uploaded_file($temp_file,$img_path)) { 1717 $is_upload = true; 1818 } else { 1919 $msg = '上传出错!'; 2020 } 2121 } else { 2222 $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!'; 2323 } 2424 } else { 2525 $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; 2626 } 2727}
《黑客&网络安全入门&进阶学习资源包》分享 (qq.com)
2.1.3绕过方法:
如图成功上传了phps文件。
黑名单容易出现大小写(pHp)、特殊可解析后缀(pht)、.htaccess、点、空格、::DATA、双写等绕过问题。
另外可能使用Intruder模块进行枚举后缀名,如使用字典
另外,“判断文件后缀名是否存在黑名单中的字符,将对应的字符串替换为空”这种方式也是不可取的。
String[] blacklist={"jsp","php","exe","dll","vxd","html"};//后缀名黑名单
11String[] blacklist={"jsp","php","exe","dll","vxd","html"};//后缀名黑名单 22for (String s : blacklist) { 33 if (suffix.indexOf(s)!=-1){ 44 suffix=suffix.replace(s,"");//后缀存在黑名单字符串,则将字符串替换为空 55 } 66}
这种方式可以通过双写 phphpp 进行绕过。
2.2白名单
2.2.1java示例:
1 1String fileSuffix = fileName.substring(fileName.lastIndexOf(".")); 2 2String[] white_suffix = {"gif","jpg","jpeg","png"}; 3 3Boolean fsFlag = false; 4 4for (String suffix:white_suffix){ 5 5 if (contentType.equalsIgnoreCase(fileSuffix)){ 6 6 fsFlag = true; 7 7 break; 8 8 } 9 9} 1010if (!fsFlag){ 1111 return "suffix not allow"; 1212}
2.2.2php示例:
1 1$is_upload = false; 2 2$msg = null; 3 3if(isset($_POST['submit'])){ 4 4 $ext_arr = array('jpg','png','gif'); 5 5 $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1); 6 6 if(in_array($file_ext,$ext_arr)){ 7 7 $temp_file = $_FILES['upload_file']['tmp_name']; 8 8 $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext; 9 9 1010 if(move_uploaded_file($temp_file,$img_path)){ 1111 $is_upload = true; 1212 } else { 1313 $msg = '上传出错!'; 1414 } 1515 } else{ 1616 $msg = "只允许上传.jpg|.png|.gif类型文件!"; 1717 } 1818}
对后缀进行白名单限制不易被绕过。
因此建议使用白名单而非黑名单。
3.检查文件类型MIME Type
3.1java示例
1 1//1、MIME检测 2 2 String contentType = file.getContentType(); 3 3 String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"}; 4 4 Boolean ctFlag = false; 5 5 for (String suffix:white_type){ 6 6 if (contentType.equalsIgnoreCase(suffix)){ 7 7 ctFlag = true; 8 8 break; 9 9 } 1010 } 1111 if (!ctFlag){ 1212 return "content-type not allow"; 1313 }
3.2php示例
11if (($_FILES['upload_file']['type'] == 'image/jpeg') || 22($_FILES['upload_file']['type'] == 'image/png') || 33($_FILES['upload_file']['type'] == 'image/gif'))
增加攻击成本,该种方式容易被绕过。
3.3绕过方法:
如图可以直接修改包。
4.使用随机数改写文件名
4.1php示例
11if(in_array($file_ext,$ext_arr)){ 22 $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext; 33 rename($upload_file, $img_path); 44 $is_upload = true;
4.2java示例
11String uuid = UUID.randomUUID().toString(); 22fileName = uuid+fileName.substring(fileName.lastIndexOf("."));
能够增加攻击成本,另外可以防范某些特殊名称文件(shell.php.rar.rar)
5.对文件内容进行校验
5.1对文件头进行校验
11.jpg FF D8 FF E0 00 10 4A 46 49 46 22.gif 47 49 46 38 39 61 33.png 89 50 4E 47
以图片为例,可以截取文件的头部几个字节进行校验,如使用getimagesize(不建议使用)
增加攻击成本,易被绕过,攻击者同样可以在文件的头部增加如下字节:
5.2对文件内容进行检测
绕过<?:
1<script language='php'>@eval($_POST[cmd]);</script>
绕过php:
1<?= @eval($_POST['cmd']);?>
同时绕过:
1<script language='pHp'>@eval($_POST[cmd]);</script>
6.对文件的处理逻辑。
若先将文件放入路径,再去检验文件合法性并删除,会存在条件竞争漏洞。
6.1示例:
1 1if(isset($_POST['submit'])){ 2 2 $ext_arr = array('jpg','png','gif'); 3 3 $file_name = $_FILES['upload_file']['name']; 4 4 $temp_file = $_FILES['upload_file']['tmp_name']; 5 5 $file_ext = substr($file_name,strrpos($file_name,".")+1); 6 6 $upload_file = UPLOAD_PATH . '/' . $file_name; 7 7 8 8 if(move_uploaded_file($temp_file, $upload_file)){ 9 9 if(in_array($file_ext,$ext_arr)){ 1010 $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext; 1111 rename($upload_file, $img_path); 1212 $is_upload = true; 1313 }else{ 1414 $msg = "只允许上传.jpg|.png|.gif类型文件!"; 1515 unlink($upload_file); 1616 } 1717 }else{ 1818 $msg = '上传出错!'; 1919 } 2020}
先用move_uploaded_file 将上传的文件移动到上传目录,再判断后缀名并就删除文件。
我们多线程异步上传文件A并不断地访问自己上传的文件A,可以文件A进行删除之前,成功执行文件A,
若文件A代码为:
1');?>
则执行成功可以生成稳定的shell.php
解决方案:经过充分完整的检查之后再上传。
7.禁止上传文件被执行
7.1存储服务器
将存储上传文件的位置设计在另一台只具备存储功能的文件服务器或数据库上,与Web应用服务器分开,这样即使木马被上传进来,也因为文件服务器不能执行脚本而没有办法实施攻击。
7.2设置权限
改存储上传文件的目录的执行脚本的权限。如linux系统下使用chmod命令修改目录的rwx权限。
7.3修改配置
修改.htaccess、apache的httpd.conf配置文件、Nginx增加配置
7.3.1 .htaccess
11<FilesMatch ".(?i:php|php3|php4|php5)"> 22Order allow,deny 33Deny from all 44</FilesMatch>
7.3.2 修改apache的配置文件httpd.conf
11<Directory D:\wwwroot\public\uploads> 22<FilesMatch ".(?i:php|php3|php4|php5)"> 33 Order allow,deny 44 Deny from all 55</FilesMatch> 66</Directory>
7.4 隐藏上传文件路径
示例:使用blob把网站上的全部图片链接加密。
后台返回图片:
1 1protected void Page_Load(object sender, EventArgs e) 2 2 { 3 3 string url = Server.MapPath("~/Images/abg.jpg"); 4 4 Bitmap b = new Bitmap(url); 5 5 MemoryStream ms = new MemoryStream(); 6 6 b.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); 7 7 Response.ClearContent(); 8 8 Response.ContentType = "image/Jpg"; 9 9 Response.BinaryWrite(ms.ToArray()); 1010 }
JavaScript代码:
1 1 <img id="img1" src=""> 2 2 <script type="text/javascript"> 3 3 //创建XMLHttpRequest对象 4 4 var xhr = new XMLHttpRequest(); 5 5 //配置请求方式、请求地址以及是否同步 6 6 xhr.open('POST', '/Default2.aspx', true); 7 7 //设置请求结果类型为blob 8 8 xhr.responseType = 'blob'; 9 9 //请求成功回调函数 1010 xhr.onload = function(e) { 1111 if (this.status == 200) {//请求成功 1212 //获取blob对象 1313 var blob = this.response; 1414 //获取blob对象地址,并把值赋给容器 1515 $("#img1").attr("src", URL.createObjectURL(blob)); 1616 } 1717 }; 1818 xhr.send(); 1919 </script>
回到网页查看img标签src的地址:
8.文件二次渲染
1 1$is_upload = false; 2 2$msg = null; 3 3if (isset($_POST['submit'])){ 4 4 // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径 5 5 $filename = $_FILES['upload_file']['name']; 6 6 $filetype = $_FILES['upload_file']['type']; 7 7 $tmpname = $_FILES['upload_file']['tmp_name']; 8 8 9 9 $target_path=UPLOAD_PATH.basename($filename); 1010 1111 // 获得上传文件的扩展名 1212 $fileext= substr(strrchr($filename,"."),1); 1313 1414 //判断文件后缀与类型,合法才进行上传操作 1515 if(($fileext == "jpg") && ($filetype=="image/jpeg")){ 1616 if(move_uploaded_file($tmpname,$target_path)) 1717 { 1818 //使用上传的图片生成新的图片 1919 $im = imagecreatefromjpeg($target_path); 2020 2121 if($im == false){ 2222 $msg = "该文件不是jpg格式的图片!"; 2323 @unlink($target_path); 2424 }else{ 2525 //给新图片指定文件名 2626 srand(time()); 2727 $newfilename = strval(rand()).".jpg"; 2828 $newimagepath = UPLOAD_PATH.$newfilename; 2929 imagejpeg($im,$newimagepath); 3030 //显示二次渲染后的图片(使用用户上传图片生成的新图片) 3131 $img_path = UPLOAD_PATH.$newfilename; 3232 @unlink($target_path); 3333 $is_upload = true; 3434 } 3535 } else { 3636 $msg = "上传出错!"; 3737 } 3838 3939 }else if(($fileext == "png") && ($filetype=="image/png")){ 4040 if(move_uploaded_file($tmpname,$target_path)) 4141 { 4242 //使用上传的图片生成新的图片 4343 $im = imagecreatefrompng($target_path); 4444 4545 if($im == false){ 4646 $msg = "该文件不是png格式的图片!"; 4747 @unlink($target_path); 4848 }else{ 4949 //给新图片指定文件名 5050 srand(time()); 5151 $newfilename = strval(rand()).".png"; 5252 $newimagepath = UPLOAD_PATH.$newfilename; 5353 imagepng($im,$newimagepath); 5454 //显示二次渲染后的图片(使用用户上传图片生成的新图片) 5555 $img_path = UPLOAD_PATH.$newfilename; 5656 @unlink($target_path); 5757 $is_upload = true; 5858 } 5959 } else { 6060 $msg = "上传出错!"; 6161 } 6262 6363 }else if(($fileext == "gif") && ($filetype=="image/gif")){ 6464 if(move_uploaded_file($tmpname,$target_path)) 6565 { 6666 //使用上传的图片生成新的图片 6767 $im = imagecreatefromgif($target_path); 6868 if($im == false){ 6969 $msg = "该文件不是gif格式的图片!"; 7070 @unlink($target_path); 7171 }else{ 7272 //给新图片指定文件名 7373 srand(time()); 7474 $newfilename = strval(rand()).".gif"; 7575 $newimagepath = UPLOAD_PATH.$newfilename; 7676 imagegif($im,$newimagepath); 7777 //显示二次渲染后的图片(使用用户上传图片生成的新图片) 7878 $img_path = UPLOAD_PATH.$newfilename; 7979 @unlink($target_path); 8080 $is_upload = true; 8181 } 8282 } else { 8383 $msg = "上传出错!"; 8484 } 8585 }else{ 8686 $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!"; 8787 } 8888}
绕过方法:
对比上传前后的两个文件,找到没有变动的区域,插入php代码。
《黑客&网络安全入门&进阶学习资源包》分享 (qq.com)
参考文章:
1 1https://wiki.wgpsec.org/knowledge/ctf/uploadfile.html 2 2 3 3https://blog.csdn.net/cldimd/article/details/104992488 4 4 5 5https://github.com/c0ny1/upload-labs 6 6 7 7https://juejin.cn/post/6989580413333159949 8 8 9 9https://blog.csdn.net/liguangyao213/article/details/123430199 1010 1111https://blog.csdn.net/qq_43277152/article/details/113373721 1212 1313https://blog.csdn.net/qq_45570082/article/details/108910162 1414 1515https://xz.aliyun.com/t/3941 1616 1717https://blog.csdn.net/weixin_64551911/article/details/124627363?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-124627363-blog-105068212.pc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-124627363-blog-105068212.pc_relevant_recovery_v2&utm_relevant_index=5 1818 1919等。
