01
文件上传破绽原理
在文件上传的性能处,若服务端脚本语言未对上传的文件进行严格验证和过滤,导致歹意用户上传歹意的脚本文件时,就有可能获取执行服务端命令的能力,这就是文件上传破绽。
02
文件上传破绽触发点
相册、头像上传、视频、照片分享、附件上传(论坛发帖、邮箱)、文件管理器等。
03
文件上传破绽的造成条件
文件可能通过前端和后端过滤和文件解决
文件内容不会扭转,可能被正确存储
存储地位在 Web 容器管制范畴内
攻击者有权限拜访存储目录并有权执行文件
只有毁坏了其中的任一条件即可避免文件上传破绽。
04
文件上传破绽常见进攻办法
0. 无防护
以下代码实现了一个简略的文件上传性能
index.html
1<form action="/upload" method="post" enctype="multipart/form-data">
2 <input type="file" name="uploadfile" >
3 <input type="submit">
4</form>
上传文件的后端解决,基于 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;
10
11@Controller
12public 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<form action="/upload" method="post" onsubmit="return judge()" enctype="multipart/form-data">
2 <input type="file" name="uploadfile" id="checkfile" >
3 <input type="submit" value="提交">
4 <p id="msg"></p>
5</form>
6<script type="text/javascript">
7
8 function judge(){9 var file=document.getElementById("checkfile").value;
10 if (file==null||file==""){11 alert("请抉择要上传的文件");
12 // location.reload(true);
13 return false;
14 }
15 var isnext=false;
16 // 定义容许上传的文件类型
17 var filetypes=[".jpg",".png"];
18 // 提取上传文件的类型,其中这里须要留神用 lastIndexOf 而非 indexOf,否则会被 1.php.php 绕过
19 var fileend=file.substring(file.lastIndexOf("."));
20 // 判断上传文件类型是否容许上传写法一
21 for (var i=0;i<filetypes.length;i++){22 if (filetypes[i]==fileend){
23 isnext=true;
24 break;
25 }
26 }
27 if (!isnext){28 document.getElementById("msg").innerHTML="文件类型不容许";
29 // location.reload(true);
30 return false;
31 }else {
32 return true;
33 }
34 // 判断上传文件类型是否容许上传写法二
35 if (fileend.indexOf(filetypes) == -1) {
36 var errMsg = "该文件不容许上传";
37 alert(errMsg);
38 return false;
39 }
40 }
41</script>
绕过办法:
这里限度了只能上传.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);// 去除字符串::$DATA
11 $file_ext = trim($file_ext); // 收尾去空
12
13 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}
《黑客 & 网络安全入门 & 进阶学习资源包》分享 (qq.com)
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;
9
10 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}
对后缀进行白名单限度不易被绕过。
因而倡议应用白名单而非黑名单。
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 46
2.gif 47 49 46 38 39 61
3.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 示例:
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<FilesMatch ".(?i:php|php3|php4|php5)">
2Order allow,deny
3Deny from all
4</FilesMatch>
7.3.2 批改 apache 的配置文件 httpd.conf
1<Directory D:\wwwroot\public\uploads>
2<FilesMatch ".(?i:php|php3|php4|php5)">
3 Order allow,deny
4 Deny from all
5</FilesMatch>
6</Directory>
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 <img id="img1" src="">
2 <script type="text/javascript">
3 // 创立 XMLHttpRequest 对象
4 var xhr = new XMLHttpRequest();
5 // 配置申请形式、申请地址以及是否同步
6 xhr.open('POST', '/Default2.aspx', true);
7 // 设置申请后果类型为 blob
8 xhr.responseType = 'blob';
9 // 申请胜利回调函数
10 xhr.onload = function(e) {11 if (this.status == 200) {// 申请胜利
12 // 获取 blob 对象
13 var blob = this.response;
14 // 获取 blob 对象地址,并把值赋给容器
15 $("#img1").attr("src", URL.createObjectURL(blob));
16 }
17 };
18 xhr.send();
19 </script>
回到网页查看 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);
10
11 // 取得上传文件的扩展名
12 $fileext= substr(strrchr($filename,"."),1);
13
14 // 判断文件后缀与类型,非法才进行上传操作
15 if(($fileext == "jpg") && ($filetype=="image/jpeg")){16 if(move_uploaded_file($tmpname,$target_path))
17 {
18 // 应用上传的图片生成新的图片
19 $im = imagecreatefromjpeg($target_path);
20
21 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 }
38
39 }else if(($fileext == "png") && ($filetype=="image/png")){40 if(move_uploaded_file($tmpname,$target_path))
41 {
42 // 应用上传的图片生成新的图片
43 $im = imagecreatefrompng($target_path);
44
45 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 }
62
63 }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/123430199
10
11https://blog.csdn.net/qq_43277152/article/details/113373721
12
13https://blog.csdn.net/qq_45570082/article/details/108910162
14
15https://xz.aliyun.com/t/3941
16
17https://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
18
19 等。