环境配置 在IDEA中创建Spring项目,在包下创建如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.example.demo;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.context.ContextLoader;import org.springframework.web.context.WebApplicationContext;import org.springframework.web.multipart.MultipartFile;import org.springframework.web.multipart.commons.CommonsMultipartFile;import javax.servlet.ServletContext;import javax.servlet.http.HttpServletRequest;import java.util.Date;import java.io.*;@RestController public class Streamupload { @RequestMapping("/streamupload") public String fileUpload (@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException { System.out.println(request.getServletContext().getRealPath(File.separator)); System.out.println("fileName: " +file.getOriginalFilename()); try { OutputStream os = new FileOutputStream("./upload/tmp" +new Date().getTime()+file.getOriginalFilename()); InputStream is = file.getInputStream(); int temp; while ((temp=is.read())!=-1 ){ os.write(temp); } os.flush(); os.close(); is.close(); }catch (FileNotFoundException e){ e.printStackTrace(); } return "success!" ; } }
在DemoApplication
中点击开始即可运行Spring程序,接着创建upload.html上传文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <form method ="POST" enctype ="multipart/form-data" action ="http://192.168.43.238:7070/streamupload" > <input type ="file" name ="file" > <input type ="submit" name ="submit" > </form > </body > </html >
上传成功返回success
java代码中常见的文件上传方式 文件流上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @RestController public class Streamupload { @RequestMapping("/streamupload") public String fileUpload (@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException { long startTime = System.currentTimeMillis(); System.out.println(request.getServletContext().getRealPath(File.separator)); System.out.println("fileName: " +file.getOriginalFilename()); try { OutputStream os = new FileOutputStream("./upload/tmp" +new Date().getTime()+file.getOriginalFilename()); InputStream is = file.getInputStream(); int temp; while ((temp=is.read())!=-1 ){ os.write(temp); } os.flush(); os.close(); is.close(); }catch (FileNotFoundException e){ e.printStackTrace(); } return "success!" ; } }
ServletFileUpload上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @RestController public class ServletUpload { @RequestMapping("/supload") public String fileupload (@RequestParam("file") MultipartFile file, HttpServletRequest request) throws FileUploadException, IOException { String realPath = "./upload" ; String tempPath = "./tmp" ; File f = new File(realPath); if (!f.exists()&&!f.isDirectory()){ f.mkdir(); } File f1 = new File(tempPath); if (!f1.isDirectory()){ f1.mkdir(); } DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setRepository(f1); ServletFileUpload upload = new ServletFileUpload(factory); upload.setHeaderEncoding("UTF-8" ); if (!ServletFileUpload.isMultipartContent(request)){return "error" ;} List<FileItem> items = upload.parseRequest(request); for (FileItem item:items){ if (item.isFormField()){ String fieldName = item.getFieldName(); String fieldValue = item.getString("UTF-8" ); }else { String fileName = item.getName(); if (fileName==null ||"" .equals(fileName.trim())){continue ;} fileName = fileName.substring(fileName.lastIndexOf("/" )+1 ); String filePath = realPath+"/" +fileName; InputStream in = item.getInputStream(); OutputStream out = new FileOutputStream(filePath); byte b[]=new byte [1024 ]; int len = -1 ; while ((len=in.read(b))!=-1 ){ out.write(b,0 ,len); } out.close(); in.close(); try { Thread.sleep(3000 ); }catch (InterruptedException e){ e.printStackTrace(); } item.delete(); } } return "success" ; } }
MultipartFile方式上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @RestController public class MultiUpload { @RequestMapping("/mupload") public String handleFileUpload (@RequestParam("file") MultipartFile file) { if (file.isEmpty()){ return "请上传文件" ; } String fileName = file.getOriginalFilename(); String suffix = fileName.substring(fileName.indexOf("." )); String filePath = System.getProperty("user.dir" )+"/tmp" ; File dest = new File(filePath+File.separator+fileName); if (!dest.getParentFile().exists()){ dest.getParentFile().mkdirs(); } try { file.transferTo(dest); return "上传成功" ; } catch (IOException e) { e.printStackTrace(); return "上传失败" ; } } }
文件上传漏洞简介 任意文件上传漏洞的本质是在进行文件上传操作时未对文件类型进行检测或检测功能不规范导致被绕过,从而使攻击者上传的可执行脚本(WebShell)被上传至服务器并成功解析。
漏洞危害:获取WebShell,攻击内网,破坏服务器数据等。
一个简单的存在文件上传漏洞的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @RestController public class MultiUpload { @RequestMapping("/mupload") public String handleFileUpload (@RequestParam("file") MultipartFile file) { if (file.isEmpty()){ return "请上传文件" ; } String fileName = file.getOriginalFilename(); String suffix = fileName.substring(fileName.indexOf("." )); String contentType = file.getContentType(); String[] picBlack = {".jsp" ,".jspx" ,".bat" ,".exe" ,".vbs" }; String[] white_type = {"image/gif" ,"image/jpeg" ,"image/jpg" ,"image/png" }; Boolean BlackFlag = false ; Boolean WhiteFlag = false ; for (String black_suffix:picBlack){ if (suffix.toLowerCase().equals(black_suffix)){ BlackFlag = true ; break ; } } for (String white_suffix:white_type){ if (contentType.toLowerCase().equals(white_suffix)){ WhiteFlag = true ; break ; } } if (BlackFlag||!WhiteFlag){ return "File Type not allow" ; } String filePath = System.getProperty("user.dir" )+"/tmp" ; File dest = new File(filePath+File.separator+fileName); if (!dest.getParentFile().exists()){ dest.getParentFile().mkdirs(); } try { file.transferTo(dest); return "上传成功" ; } catch (IOException e) { e.printStackTrace(); return "上传失败" ; } } }
上面的代码就是很典型的文件上传漏洞,只对第一个.后面的字符串进行黑名单拦截,及其容易被绕过,如.txt.jsp
文件上传漏洞修复
对上传文件进行随机字符串重命名
用白名单限制文件后缀
去除文件名中的特殊字符
如果是上传图片,使用图片库对文件内容进行检测
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @RestController public class securityUpload { @RequestMapping("/securityUpload") public String handleFileUpload (@RequestParam("file") MultipartFile file) { if (file.isEmpty()){ return "请上传文件" ; } String fileName = file.getOriginalFilename(); String suffix = fileName.substring(fileName.lastIndexOf("." )); String contentType = file.getContentType(); String[] picWhite = {".png" ,".jpg" ,".gif" ,".webp" ,".bmp" }; String[] white_type = {"image/gif" ,"image/jpeg" ,"image/jpg" ,"image/png" }; Boolean SuffixFlag = false ; Boolean TypeFlag = false ; for (String pic_suffix:picWhite){ if (suffix.toLowerCase().equals(pic_suffix)){ SuffixFlag = true ; break ; } } for (String white_suffix:white_type){ if (contentType.toLowerCase().equals(white_suffix)){ TypeFlag = true ; break ; } } if (!SuffixFlag||!TypeFlag){ return "File Type not allow" ; } String filePath = System.getProperty("user.dir" )+"/tmp" ; Date date = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddhhmmss" ); String newfileName = dateFormat.format(date)+Integer.toHexString((int )new Date().getTime())+suffix; File dest = new File(filePath+File.separator+newfileName); if (!dest.getParentFile().exists()){ dest.getParentFile().mkdirs(); } try { file.transferTo(dest); return "上传成功" ; } catch (IOException e) { e.printStackTrace(); return "上传失败" ; } } }
代码审计中文件上传关键字 1 2 3 4 5 6 7 org.apache.commons.fileupload java.io.File MultipartFile RequestMethod MultipartHttpServletRequest CommonsMultipartResolver ...