• 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

  • 相关阅读:
    [转]utf8编码原理详解
    [转]程序员趣味读物:谈谈Unicode编码
    confluence 安装部署
    统计日志10分钟内出现的次数
    Error occurred during initialization of VM Could not reserve enough space for object heap(可能是内核参数的问题)
    Linux服务器性能查看分析调优
    python break ,continue和 pass 语句(八)
    python 循环基础(六)
    python 循环实例(七)
    python 条件控制(五)
  • 原文地址:https://www.cnblogs.com/N0r4h/p/15955736.html
Copyright © 2020-2023  润新知