【小白必学】文件上传的漏洞介绍及常见防御方法

01文件上传漏洞原理
在文件上传的功能处,若服务端脚本语言未对上传的文件进行严格验证和过滤,导致恶意用户上传恶意的脚本文件时,就有可能获取执行服务端命令的能力,这就是文件上传漏洞。
02文件上传漏洞触发点
相册、头像上传、视频、照片分享、附件上传(论坛发帖、邮箱)、文件管理器等。
03文件上传漏洞的形成条件
文件能够通过前端和后端过滤和文件处理
文件内容不会改变,能够被正确存储
存储位置在Web容器控制范围内
攻击者有权限访问存储目录并有权执行文件
只要破坏了其中的任一条件即可防止文件上传漏洞。
04文件上传漏洞常见防御方法
0.无防护
以下代码实现了一个简单的文件上传功能
index.html
1
上传文件的后端处理,基于Spring Boot
1package com.example.fileuploadvul.Controller; 2 3import org.springframework.stereotype.Controller; 4import org.springframework.web.bind.annotation.PostMapping; 5import org.springframework.web.bind.annotation.RequestParam; 6import org.springframework.web.multipart.MultipartFile; 7 8import java.io.File; 9import java.io.IOException;1011@Controller12public class UploadFile {13 @PostMapping("/upload")14 public String uploadFile(@RequestParam("uploadfile")MultipartFile file){15//获取文件名16 String filename = file.getOriginalFilename();17 //文件保存路径18 String path="F:\\tmp\\";19 File outfile = new File(path + filename);20 try {21 file.transferTo(outfile);22 }catch (IOException e){23 e.printStackTrace();24 }25 return "success";26 }27}
上面的代码没有任何的防护功能,存在文件上传漏洞。用户可以随意的上传任何文件、木马。
1.前端进行js校验
增加攻击成本,该种方式容易被绕过。
javascript示例:
1
6绕过方法:
这里限制了只能上传.jpg .png文件,但是攻击者可以用burpsuite拦截修改包进行绕过。
如图,上传一个内容为php的jpg文件,然后在此处将filename修改为1.php,即可绕过前端js验证。
另外也可以利用插件禁用js后进行提交。
2.白名单/黑名单
2.1黑名单
2.1.1java示例:
1@Controller 2public class UploadFile { 3 @PostMapping("/upload") 4 public String uploadFile(@RequestParam("uploadfile")MultipartFile file, Model model){ 5 boolean flag=true; 6 String filename = file.getOriginalFilename(); 7 System.out.println(filename); 8 String suffix=filename.substring(filename.lastIndexOf(".")); 9 String[] blacklist={".jsp",".php",".exe",".dll","vxd","html"};//后缀名黑名单10 for (String s : blacklist) {11 if (suffix.equals(s)){12 flag=false;13 break;14 }15 }16 if (flag){17 String path="src\\main\\resources\\static\\upload";18 File fileDir = new File(path);19 File outfile = new File(fileDir.getAbsolutePath()+File.separator + filename);20 try {21 file.transferTo(outfile);22 return "success";23 }catch (IOException e){24 e.printStackTrace();25 }26 }27 else {28 model.addAttribute("msg","非法文件类型");29 }30 return "index";31 }32}
2.1.2php示例:
1$is_upload = false; 2$msg = null; 3if (isset($_POST['submit'])) { 4 if (file_exists(UPLOAD_PATH)) { 5 $deny_ext = array('.asp','.aspx','.php','.jsp'); 6 $file_name = trim($_FILES['upload_file']['name']); 7 $file_name = deldot($file_name);//删除文件名末尾的点 8 $file_ext = strrchr($file_name, '.'); 9 $file_ext = strtolower($file_ext); //转换为小写10 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA11 $file_ext = trim($file_ext); //收尾去空1213 if(!in_array($file_ext, $deny_ext)) {14 $temp_file = $_FILES['upload_file']['tmp_name'];15 $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; 16 if (move_uploaded_file($temp_file,$img_path)) {17 $is_upload = true;18 } else {19 $msg = '上传出错!';20 }21 } else {22 $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';23 }24 } else {25 $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';26 }27}
2.1.3绕过方法:
如图成功上传了phps文件。
黑名单容易出现大小写(pHp)、特殊可解析后缀(pht)、.htaccess、点、空格、::DATA、双写等绕过问题。
另外可能使用Intruder模块进行枚举后缀名,如使用字典
另外,“判断文件后缀名是否存在黑名单中的字符,将对应的字符串替换为空”这种方式也是不可取的。
String[] blacklist={"jsp","php","exe","dll","vxd","html"};//后缀名黑名单
1String[] blacklist={"jsp","php","exe","dll","vxd","html"};//后缀名黑名单2for (String s : blacklist) {3 if (suffix.indexOf(s)!=-1){4 suffix=suffix.replace(s,"");//后缀存在黑名单字符串,则将字符串替换为空5 }6}
这种方式可以通过双写 phphpp 进行绕过。
2.2白名单
2.2.1java示例:
1String fileSuffix = fileName.substring(fileName.lastIndexOf(".")); 2String[] white_suffix = {"gif","jpg","jpeg","png"}; 3Boolean fsFlag = false; 4for (String suffix:white_suffix){ 5 if (contentType.equalsIgnoreCase(fileSuffix)){ 6 fsFlag = true; 7 break; 8 } 9}10if (!fsFlag){11 return "suffix not allow";12}
2.2.2php示例:
1$is_upload = false; 2$msg = null; 3if(isset($_POST['submit'])){ 4 $ext_arr = array('jpg','png','gif'); 5 $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1); 6 if(in_array($file_ext,$ext_arr)){ 7 $temp_file = $_FILES['upload_file']['tmp_name']; 8 $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext; 910 if(move_uploaded_file($temp_file,$img_path)){11 $is_upload = true;12 } else {13 $msg = '上传出错!';14 }15 } else{16 $msg = "只允许上传.jpg|.png|.gif类型文件!";17 }18}
对后缀进行白名单限制不易被绕过。
因此建议使用白名单而非黑名单。
《黑客&网络安全入门&进阶学习资源包》分享 (qq.com)
3.检查文件类型MIME Type
3.1java示例
1//1、MIME检测 2 String contentType = file.getContentType(); 3 String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"}; 4 Boolean ctFlag = false; 5 for (String suffix:white_type){ 6 if (contentType.equalsIgnoreCase(suffix)){ 7 ctFlag = true; 8 break; 9 }10 }11 if (!ctFlag){12 return "content-type not allow";13 }
3.2php示例
1if (($_FILES['upload_file']['type'] == 'image/jpeg') || 2($_FILES['upload_file']['type'] == 'image/png') || 3($_FILES['upload_file']['type'] == 'image/gif'))
增加攻击成本,该种方式容易被绕过。
3.3绕过方法:
如图可以直接修改包。
4.使用随机数改写文件名
4.1php示例
1if(in_array($file_ext,$ext_arr)){2 $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;3 rename($upload_file, $img_path);4 $is_upload = true;
4.2java示例
1String uuid = UUID.randomUUID().toString();2fileName = uuid+fileName.substring(fileName.lastIndexOf("."));
能够增加攻击成本,另外可以防范某些特殊名称文件(shell.php.rar.rar)
5.对文件内容进行校验
5.1对文件头进行校验
1.jpg FF D8 FF E0 00 10 4A 46 49 462.gif 47 49 46 38 39 613.png 89 50 4E 47
以图片为例,可以截取文件的头部几个字节进行校验,如使用getimagesize(不建议使用)
增加攻击成本,易被绕过,攻击者同样可以在文件的头部增加如下字节:
5.2对文件内容进行检测
绕过:
1
绕过php:
1= @eval($_POST['cmd']);?>
同时绕过:
1
6.对文件的处理逻辑。
若先将文件放入路径,再去检验文件合法性并删除,会存在条件竞争漏洞。
6.1示例:
1if(isset($_POST['submit'])){ 2 $ext_arr = array('jpg','png','gif'); 3 $file_name = $_FILES['upload_file']['name']; 4 $temp_file = $_FILES['upload_file']['tmp_name']; 5 $file_ext = substr($file_name,strrpos($file_name,".")+1); 6 $upload_file = UPLOAD_PATH . '/' . $file_name; 7 8 if(move_uploaded_file($temp_file, $upload_file)){ 9 if(in_array($file_ext,$ext_arr)){10 $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;11 rename($upload_file, $img_path);12 $is_upload = true;13 }else{14 $msg = "只允许上传.jpg|.png|.gif类型文件!";15 unlink($upload_file);16 }17 }else{18 $msg = '上传出错!';19 }20}
先用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
1
7.3.2 修改apache的配置文件httpd.conf
1
7.4 隐藏上传文件路径
示例:使用blob把网站上的全部图片链接加密。
后台返回图片:
1protected void Page_Load(object sender, EventArgs e) 2 { 3 string url = Server.MapPath("~/Images/abg.jpg"); 4 Bitmap b = new Bitmap(url); 5 MemoryStream ms = new MemoryStream(); 6 b.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); 7 Response.ClearContent(); 8 Response.ContentType = "image/Jpg"; 9 Response.BinaryWrite(ms.ToArray());10 }
JavaScript代码:
1 2
回到网页查看img标签src的地址:
8.文件二次渲染
1$is_upload = false; 2$msg = null; 3if (isset($_POST['submit'])){ 4 // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径 5 $filename = $_FILES['upload_file']['name']; 6 $filetype = $_FILES['upload_file']['type']; 7 $tmpname = $_FILES['upload_file']['tmp_name']; 8 9 $target_path=UPLOAD_PATH.basename($filename);1011 // 获得上传文件的扩展名12 $fileext= substr(strrchr($filename,"."),1);1314 //判断文件后缀与类型,合法才进行上传操作15 if(($fileext == "jpg") && ($filetype=="image/jpeg")){16 if(move_uploaded_file($tmpname,$target_path))17 {18 //使用上传的图片生成新的图片19 $im = imagecreatefromjpeg($target_path);2021 if($im == false){22 $msg = "该文件不是jpg格式的图片!";23 @unlink($target_path);24 }else{25 //给新图片指定文件名26 srand(time());27 $newfilename = strval(rand()).".jpg";28 $newimagepath = UPLOAD_PATH.$newfilename;29 imagejpeg($im,$newimagepath);30 //显示二次渲染后的图片(使用用户上传图片生成的新图片)31 $img_path = UPLOAD_PATH.$newfilename;32 @unlink($target_path);33 $is_upload = true;34 }35 } else {36 $msg = "上传出错!";37 }3839 }else if(($fileext == "png") && ($filetype=="image/png")){40 if(move_uploaded_file($tmpname,$target_path))41 {42 //使用上传的图片生成新的图片43 $im = imagecreatefrompng($target_path);4445 if($im == false){46 $msg = "该文件不是png格式的图片!";47 @unlink($target_path);48 }else{49 //给新图片指定文件名50 srand(time());51 $newfilename = strval(rand()).".png";52 $newimagepath = UPLOAD_PATH.$newfilename;53 imagepng($im,$newimagepath);54 //显示二次渲染后的图片(使用用户上传图片生成的新图片)55 $img_path = UPLOAD_PATH.$newfilename;56 @unlink($target_path);57 $is_upload = true; 58 }59 } else {60 $msg = "上传出错!";61 }6263 }else if(($fileext == "gif") && ($filetype=="image/gif")){64 if(move_uploaded_file($tmpname,$target_path))65 {66 //使用上传的图片生成新的图片67 $im = imagecreatefromgif($target_path);68 if($im == false){69 $msg = "该文件不是gif格式的图片!";70 @unlink($target_path);71 }else{72 //给新图片指定文件名73 srand(time());74 $newfilename = strval(rand()).".gif";75 $newimagepath = UPLOAD_PATH.$newfilename;76 imagegif($im,$newimagepath);77 //显示二次渲染后的图片(使用用户上传图片生成的新图片)78 $img_path = UPLOAD_PATH.$newfilename;79 @unlink($target_path);80 $is_upload = true;81 }82 } else {83 $msg = "上传出错!";84 }85 }else{86 $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";87 }88}
绕过方法:
对比上传前后的两个文件,找到没有变动的区域,插入php代码。
《黑客&网络安全入门&进阶学习资源包》分享 (qq.com)
参考文章:
1https://wiki.wgpsec.org/knowledge/ctf/uploadfile.html 2 3https://blog.csdn.net/cldimd/article/details/104992488 4 5https://github.com/c0ny1/upload-labs 6 7https://juejin.cn/post/6989580413333159949 8 9https://blog.csdn.net/liguangyao213/article/details/1234301991011https://blog.csdn.net/qq_43277152/article/details/1133737211213https://blog.csdn.net/qq_45570082/article/details/1089101621415https://xz.aliyun.com/t/39411617https://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=51819等。