Java代码审计 | 您所在的位置:网站首页 › filegetoriginalfilename乱码 › Java代码审计 |
环境配置
Springboot:2.7.5 依赖 ``` org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.apache.tomcat.embed tomcat-embed-jasper commons-fileupload commons-fileupload 1.2.2 commons-io commons-io 2.0.1 ``` application.yml ``` spring: mvc: view: prefix: /WEB-INF/jsp/ suffix: .jsp web: resources: static-locations: classpath:/templates/ server: port: 8081 ``` 前置知识 multipart/form-datamultipart/form-data这种编码方式的表单会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数里。通常会见到配合method=post去搭配使用,而后端采取inputstream等方式读取客户端传入的二进制流来处理文件。 00截断问题PHP中:PHP\ 上传入口MultipartFile上传 ServletFileUpload上传基于Commons-FileUpload组件 依赖 commons-fileupload commons-fileupload 1.2.2 Springboot环境需关闭multipart spring: servlet: multipart: enabled: false 创建步骤 创建磁盘工厂:DiskFileItemFactory factory = new DiskFileItemFactory(); 创建处理工具:ServletFileUpload upload = new ServletFileUpload(factory); 设置上传文件大小:upload.setFileSizeMax(3145728); 接收全部内容:List items = upload.parseRequest(request);``` @RequestMapping("/upload3") protected void ServletFileUpload(HttpServletRequest request, HttpServletResponse response) throws IOException { { //设置文件上传路径 String filePath = request.getServletContext().getRealPath("upload"); File uploadFile = new File(filePath); //若不存在该路径则创建之 if (!uploadFile.exists() && !uploadFile.isDirectory()) { uploadFile.mkdir(); } try { //创建一个磁盘工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //创建文件上传解析器 ServletFileUpload fileupload = new ServletFileUpload(factory); //三个照顾要上传的文件大小 fileupload.setFileSizeMax(3145728); //判断是否为multipart/form-data类型,为false则直接跳出该方法 if (!fileupload.isMultipartContent(request)) { return; } //使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List集合,每一个FileItem对应一个Form表单的输入项 List items = fileupload.parseRequest(request); for (FileItem item : items) { //isFormField方法用于判断FileItem类对象封装的数据是否属于一个普通表单字段,还是属于一个文件表单字段,如果是普通表单字段则返回true,否则返回false。 if (item.isFormField()) { String name = item.getFieldName(); //解决普通输入项的数据的中文乱码问题 String value = item.getString("UTF-8"); String value1 = new String(name.getBytes("iso8859-1"), "UTF-8"); System.out.println(name + " : " + value); System.out.println(name + " : " + value1); } else { //获得上传文件名称 String fileName = item.getName(); System.out.println(fileName); if (fileName == null || fileName.trim().equals("")) { continue; } //注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt //处理获取到的上传文件的文件名的路径部分,只保留文件名部分 fileName = fileName.substring(fileName.lastIndexOf(File.separator) + 1); //获取item中的上传文件的输入流 InputStream is = item.getInputStream(); FileOutputStream fos = new FileOutputStream(filePath + File.separator + fileName); byte buffer[] = new byte[1024]; int length = 0; while ((length = is.read(buffer)) > 0) { fos.write(buffer, 0, length); } is.close(); fos.close(); item.delete(); } } response.getWriter().write("Success!"); } catch (FileUploadException e) { e.printStackTrace(); } } } ``` 上传入口ServletFileUpload上传 Servlet Part上传Servlet3之后,有提出了request.getParts()获取上传文件的方式。 除此外若加上注解@MultipartConfig,则可定义一些上传属性 | 方法 | 类型 | 是否可选 | 作用 | | ----------------------- | ------------ | -------------- | -------------------------------------------------------------------- | | fileSizeThershold | int | 是 | 当前数据量大于该值时,内容将被写入文件 | | location | String | 是 | 存放文件的路径 | | maxFileSize | long | 是 | 允许上传的文件最大值,默认为-1,表示没有限制 | | maxRequestSize | long | 是 | 针对multipart/form-data 请求的最大数量,默认为-1,表示没有限制 | ServletPart常用方法 String getName() 获取这部分的名称,例如相关表单域的名称 String getContentType() 如果Part是一个文件,那么将返回Part的内容类型,否则返回null(可以利用这一方法来识别是否为文件域) Collection getHeaderNames() 返回这个Part中所有标头的名称 String getHeader(String headerName) 返回指定标头名称的值 void write(String path) 将上传的文件写入服务器中项目的指定地址下,如果path是一个绝对路径,那么将写入指定的路径,如果path是一个相对路径,那么将被写入相对于location属性值的指定路径。 InputStream getInputStream() 以inputstream的形式返回上传文件的内容@RequestMapping("/upload4") public void ServletPartUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String filePath = request.getServletContext().getRealPath("upload"); File uploadFile = new File(filePath); //若不存在该路径则创建之 if (!uploadFile.exists() && !uploadFile.isDirectory()) { uploadFile.mkdir(); } //通过表单中name属性值,获取filename Part part = request.getPart("file"); if(part == null) { return ; } String filename = filePath + File.separator + part.getSubmittedFileName(); part.write(filename); part.delete(); } 文件上传入口ServletPart上传 文件上传漏洞上述都是no waf的文件上传方式,若不做任何防御的情况下,可以实现任意文件上传,造成文件上传漏洞 通过上述任意方法,上传jsp马 执行成功 防御 content-type白名单//1、MIME检测 String contentType = file.getContentType(); String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"}; Boolean ctFlag = false; for (String suffix:white_type){ if (contentType.equalsIgnoreCase(suffix)){ ctFlag = true; break; } } if (!ctFlag){ return "content-type not allow"; } 如果单设置这一个的话其实很好绕过 重命名文件可以用uuid、md5、时间戳等方式 //2、重命名文件 String uuid = UUID.randomUUID().toString(); fileName = uuid+fileName.substring(fileName.lastIndexOf("."));; 后缀白名单//3、后缀白名单 String fileSuffix = fileName.substring(fileName.lastIndexOf(".")); String[] white_suffix = {"gif","jpg","jpeg","png"}; Boolean fsFlag = false; for (String suffix:white_suffix){ if (contentType.equalsIgnoreCase(fileSuffix)){ fsFlag = true; break; } } if (!fsFlag){ return "suffix not allow"; } 绕过MIME检测后,可以通过白名单进行进一步的防御 修改存储位置可以将图片存放到不可访问的路径,例如:Servlet的WEB-INF下,默认情况是访问不到的 //4、修改存储位置 String filePath = request.getServletContext().getRealPath("/WEB-INF/upload"); 最终代码 ``` public String MultiFileUpload(@RequestParam("file") MultipartFile file ,HttpServletRequest request) { if (file.isEmpty()) { return "请上传文件"; } // String filePath = request.getServletContext().getRealPath("upload"); String fileName = file.getOriginalFilename(); //1、MIME检测 String contentType = file.getContentType(); String[] white_type = {"image/gif","image/jpeg","image/jpg","image/png"}; Boolean ctFlag = false; for (String suffix:white_type){ if (contentType.equalsIgnoreCase(suffix)){ ctFlag = true; break; } } if (!ctFlag){ return "content-type not allow"; } //2、重命名文件 String uuid = UUID.randomUUID().toString(); fileName = uuid+fileName.substring(fileName.lastIndexOf("."));; //3、后缀白名单 String fileSuffix = fileName.substring(fileName.lastIndexOf(".")); String[] white_suffix = {"gif","jpg","jpeg","png"}; Boolean fsFlag = false; for (String suffix:white_suffix){ if (contentType.equalsIgnoreCase(fileSuffix)){ fsFlag = true; break; } } if (!fsFlag){ return "suffix not allow"; } //4、修改存储位置 String filePath = request.getServletContext().getRealPath("/WEB-INF/upload/"); File dest = new File(filePath + File.separator + fileName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } try { file.transferTo(dest); return "Success!"; } catch (IOException e) { e.printStackTrace(); } return ""; } ``` 代码审计中常见文件上传关键字DiskFileItemFactory @MultipartConfig MultipartFile File upload InputStream OutputStream write fileName filePath |
CopyRight 2018-2019 实验室设备网 版权所有 |