JAVA审计-文件上传

前言

本篇记录关于java审计中文件上传部分。

0x01 Commons-FileUpload组件

利用这个组件上传是平时代码上传中遇到最多的。

关于Commons-FileUpload

Commons-FileUpload是Apache的一个组件,依赖于Commons-io,也是目前用的比较广泛的一个文件上传组件之一。

Spring MVCStruts2Tomcat等底层处理文件上传请求都是使用的这个库。

FileUpload上传的基本步骤:

  • 创建磁盘工厂:DiskFileItemFactory factory = new DiskFileItemFactory();

  • 创建处理工具:ServletFileUpload upload = new ServletFileUpload(factory);

  • 设置上传文件大小:upload.setFileSizeMax(3145728);

  • 接收全部内容:List items = upload.parseRequest(request);

Commons-FileUpload上传

示例servlet:

@Controller @WebServlet("/upload") public class FileUpload extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //设置文件上传路径 String uploadDir = this.getServletContext().getRealPath("/upload/"); File uploadFile = new File(uploadDir); //若不存在该路径则创建之 if (!uploadFile.exists()&&!uploadFile.isDirectory()){ uploadFile.mkdir(); } String message = ""; try { //创建一个磁盘工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //创建文件上传解析器 ServletFileUpload fileupload = new ServletFileUpload(factory); //设置上传的文件大小 fileupload.setFileSizeMax(3145728); //判断是否为multipart/form-data类型,为false则直接跳出该方法 if (!fileupload.isMultipartContent(req)){ return; } //使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项 List<FileItem> items = fileupload.parseRequest(req); 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(uploadDir+File.separator+fileName); //创建一个缓冲区 byte buffer[] = new byte[1024]; //判断输入流中的数据是否已经读完的标识 int length = 0; //循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据 while((length = is.read(buffer))>0){ //使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" + filename)当中 fos.write(buffer, 0, length); } //关闭输入流 is.close(); //关闭输出流 fos.close(); //删除处理文件上传时生成的临时文件 item.delete(); message = "文件上传成功"; } } } catch (FileUploadException e) { message = "文件上传失败"; e.printStackTrace(); } } }

fileupload.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="/jdk1_7vulns_war_exploded/upload" enctype="multipart/form-data" method="post"> <p> 用户名: <input name="username" type="text"/> 文件: <input id="file" name="file" type="file"/> </p> <input name="submit" type="submit" value="Submit"/> </form> </body> </html>

上面的代码里面对于文件处理只判断了文件是否为空,没有对文件类型进行限制,导致了任意文件上传:

image-20220302151951112

image-20220302152048641

image-20220302152102939

实际中主要是看对后缀的限制,还有一些代码通过new File(item.getContentType()).getName()得到contentType值判断,也是不严谨的。

0x02 SpringMVC MultipartResolver

使用MultipartResolver

MultipartResolver是专门处理文件上传的一个类

使用之前需要再pom中引入包:

<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.0.1</version> </dependency>

MultipartResolver在上下文中没有被装配,需要手动装配MultipartResolver,在springMVC的配置文件dispatcher-Servlet.xml中进行以下的配置:

<!--文件上传配置--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 --> <property name="defaultEncoding" value="utf-8"/> <!-- 上传文件大小上限,单位为字节(10485760=10M) --> <property name="maxUploadSize" value="10485760"/> <property name="maxInMemorySize" value="40960"/> </bean>

CommonsMultipartFile常用方法:

  • String getOriginalFilename():获取上传文件的原名
  • InputStream getInputStream():获取文件流
  • void transferTo(File dest):将上传文件保存到一个目录文件中

MultipartResolver+IO

示例代码:

@Controller public class FileUploadController { //@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象 @RequestMapping("/upload") public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException { //获取文件名 : file.getOriginalFilename(); String uploadFileName = file.getOriginalFilename(); if ("".equals(uploadFileName)){ return "redirect:/index.jsp"; } System.out.println("上传文件名 : "+uploadFileName); //上传路径 String path = request.getServletContext().getRealPath("/upload"); //如果路径不存在,创建一个 File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } System.out.println("上传文件地址:"+realPath); InputStream is = file.getInputStream(); OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //读取写出 int len=0; byte[] buffer = new byte[1024]; while ((len=is.read(buffer))!=-1){ os.write(buffer,0,len); os.flush(); } os.close(); is.close(); return "redirect:/index.jsp"; }

fileupload2.jsp

<form action="/jdk1_7vulns_war_exploded/upload2" enctype="multipart/form-data" method="post"> <input type="file" name="file"/> <input type="submit" value="upload"> </form>

@RequestParam("file") CommonsMultipartFile file直接在前端传入过来的时候就封装成 CommonsMultipartFile 对象处理

image-20220302160905606

image-20220302160931733

CommonsMultipartFile+transferTo

@RequestMapping("/upload2") public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException { //上传路径保存设置 String path = request.getServletContext().getRealPath("/upload"); File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } //上传文件地址 System.out.println("上传文件保存地址:"+realPath); //通过CommonsMultipartFile的方法直接写文件(注意这个时候) file.transferTo(new File(realPath +"/"+ file.getOriginalFilename())); return "redirect:/index.jsp"; }

0x03 Servlet Part

servlet3之后,可以不使用commons-fileupload、commons-io这两个jar包来处理文件上传,转而使用request.getParts()获取上传文件。

此外,servlet3还支持以注解的形式定义一些上传的属性。

image-20220302161822202

上传示例:

String savePath = request.getServletContext().getRealPath("/WEB-INF/uploadFile"); //Servlet3.0将multipart/form-data的POST请求封装成Part,通过Part对上传的文件进行操作。 Part part = request.getPart("file");//通过表单file控件(<input type="file" name="file">)的名字直接获取Part对象 //Servlet3没有提供直接获取文件名的方法,需要从请求头中解析出来 //获取请求头,请求头的格式:form-data; name="file"; filename="snmp4j--api.zip" String header = part.getHeader("content-disposition"); //获取文件名 String fileName = getFileName(header); //把文件写到指定路径 part.write(savePath+File.separator+fileName);

总结

对代码中的一些关键函数进行审计:

DiskFileItemFactory @MultipartConfig MultipartFile File upload InputStream OutputStream write fileName filePath

或者有前端代码的话,可以结合multipart/form-data进行定位

参考

https://www.cnblogs.com/CoLo/p/15225367.html

https://www.cnblogs.com/nice0e3/p/13698256.html

https://github.com/proudwind/javasec_study/blob/master/java代码审计-文件操作.md


__EOF__

本文作者N0r4h
本文链接https://www.cnblogs.com/N0r4h/p/15955736.html
关于博主:一个废物到自闭的人
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
posted @   N0r4h  阅读(392)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示