首页 > 编程语言 >Java代码审计-FileUpload

Java代码审计-FileUpload

时间:2024-01-29 15:57:38浏览次数:20  
标签:审计 FileUpload Java String req new import servlet javax

Web应用通常都会包含文件上传功能,用户可以将其本地的文件上传到Web服务器上。如果服务器端没有能够正确的检测用户上传的文件类型是否合法(例如上传了jsp后缀的WebShell)就将文件写入到服务器中就可能会导致服务器被非法入侵。

漏洞成因

后缀名无限制

// 导入必要的类库
package com.example.upload;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.UUID;

// 定义Servlet,并指定映射路径为"/upload"
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

    // 覆盖父类的service方法
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 设置请求和响应的字符编码
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");

        // 创建DiskFileItemFactory实例
        DiskFileItemFactory factory = new DiskFileItemFactory();

        // 获取临时目录,并确保目录存在
        File tempDir = new File(req.getSession().getServletContext().getRealPath("tmp"));
        if (!tempDir.exists()) {
            tempDir.mkdirs();
        }
        factory.setRepository(tempDir);

        // 创建ServletFileUpload实例,并设置字符编码
        ServletFileUpload fileUpload = new ServletFileUpload(factory);
        fileUpload.setHeaderEncoding("UTF-8");

        try {
            // 解析请求,获取FileItem列表
            List<FileItem> fileItems = fileUpload.parseRequest(req);

            // 遍历处理每个FileItem
            for (FileItem fileItem : fileItems) {
                if (fileItem.isFormField()) {
                    // 如果是普通表单字段,获取字段名和值
                    String fieldName = fileItem.getFieldName();
                    String fieldValue = fileItem.getString("utf-8");
                    // 这里可以对普通表单字段进行处理
                } else {
                    // 如果是文件字段,处理文件上传

                    // 获取原始文件名、文件扩展名、生成唯一文件名
                    String originalFileName = fileItem.getName();
                    String fileExtension = FilenameUtils.getExtension(originalFileName);
                    String fileUUIDName = UUID.randomUUID().toString();
                    String uploadedFileName = fileUUIDName + "." + fileExtension;

                    // 构建上传路径
                    String uploadPath = req.getSession().getServletContext().getRealPath("uploads/" + uploadedFileName);

                    // 创建上传文件的File对象及其父目录
                    File uploadedFile = new File(uploadPath);
                    uploadedFile.getParentFile().mkdirs();
                    uploadedFile.createNewFile();

                    // 使用输入流和输出流实现文件拷贝
                    try (FileOutputStream out = new FileOutputStream(uploadedFile);
                         InputStream in = fileItem.getInputStream()) {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = in.read(buffer)) != -1) {
                            out.write(buffer, 0, len);
                        }
                    }

                    // 删除临时文件
                    fileItem.delete();

                    // 输出上传路径
                    PrintWriter writer = resp.getWriter();
                    writer.write("上传路径:" + uploadPath);
                }
            }

        } catch (FileUploadException e) {
            // 处理文件上传异常
            throw new RuntimeException(e);
        }
    }
}

 // 获取临时目录,并确保目录存在
File tempDir = new File(req.getSession().getServletContext().getRealPath("tmp"));

req.getSession(): 获取当前请求的HttpSession对象,HttpSession是用于跟踪用户会话信息的对象。
getServletContext(): 获取HttpSession所关联的ServletContext对象,ServletContext代表了整个Web应用程序的上下文。
getRealPath("tmp"): 获取指定虚拟路径("tmp")在文件系统中的真实路径。这里指的是应用程序的临时目录。
new File(...): 用获取到的真实路径创建一个File对象,表示应用程序的临时目录。

// 创建DiskFileItemFactory实例
DiskFileItemFactory factory = new DiskFileItemFactory();

DiskFileItemFactory是Apache Commons FileUpload库中的类,用于创建FileItem对象的工厂。在文件上传过程中,FileItem对象代表了一个表单字段或文件。DiskFileItemFactory特别用于将文件项(FileItem)存储到硬盘上而不是内存中,以避免内存溢出的问题。

具体作用包括:
创建FileItem对象: 通过DiskFileItemFactory创建FileItem实例,以便在后续的文件上传过程中存储表单字段或文件的信息。
配置工厂属性: 设置工厂的属性,例如临时存储目录(Repository),这是一个用于存储上传文件的临时目录,以及其他一些配置,以确保文件上传的正常进行。

漏洞分析

// 获取文件扩展名
String fileExtension = FilenameUtils.getExtension(originalFileName);

//构建上传路径
String uploadPath = req.getSession().getServletContext().getRealPath("uploads/" + uploadedFileName);

黑白名单

黑名单

package com.example;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

@WebServlet("/upload1")
public class UploadServlet1 extends HttpServlet {
    private List<String> donExtList= Arrays.asList(".jsp",".php",".java",".class");
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置编码集(解码)
        req.setCharacterEncoding("UTF-8");
        // 设置响应的编码集
        resp.setContentType("text/html;charset=utf-8");
        DiskFileItemFactory factory = new DiskFileItemFactory();
        File f = new File(req.getSession().getServletContext().getRealPath("tmp"));
        if (!f.exists()) {
            f.mkdirs();
        }
        factory.setRepository(f);
        ServletFileUpload fileupload = new ServletFileUpload(factory);
        fileupload.setHeaderEncoding("UTF-8");
        try {
            List<FileItem> fileItems = fileupload.parseRequest(req);
            for (FileItem fileItem : fileItems) {
                if (fileItem.isFormField()) {
                    String fieldName = fileItem.getFieldName();
                    String fieldvalue = fileItem.getString("utf-8");

                } else {
                    String name = fileItem.getName();
                    String extName = "." + FilenameUtils.getExtension(name);
                    for (String s : this.donExtList) {
                        if (s.equals(extName)) {
                            resp.getWriter().write("禁止上传改类型文件");
                            return;
                        }

                    }
                    String fileUUIDName = UUID.randomUUID().toString();
                    String upFileName = fileUUIDName + extName;
                    String uploadPath = req.getSession().getServletContext().getRealPath("uploads/" + upFileName);
                    //创建文件
                    File file = new File(uploadPath);
                    file.getParentFile().mkdirs();
                    file.createNewFile();
                    //创建文件输出流
                    FileOutputStream out = new FileOutputStream(file);
                    //获取文件输入流
                    InputStream in = fileItem.getInputStream();
                    byte[] buffer = new byte[1024];
                    int len = -1;
                    while ((len = in.read(buffer)) != -1) {
                        out.write(buffer, 0, len);
                    }

                    in.close();
                    out.close();
                    //删除fileTtem对象
                    fileItem.delete();
                    PrintWriter writer = resp.getWriter();
                    writer.write("上传路径:" + uploadPath);


                }
            }

        } catch (FileUploadException e) {
            throw new RuntimeException(e);

        }

    }
}


漏洞分析

private List<String> donExtList= Arrays.asList(".jsp",".php",".java",".class");

//黑名单不全

白名单

package com.example;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

@WebServlet("/upload4")
public class UploadServlet4 extends HttpServlet {
    private List<String> donExtList= Arrays.asList(".jsp",".php",".java",".class");
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置编码集(解码)
        req.setCharacterEncoding("UTF-8");
        // 设置响应的编码集
        resp.setContentType("text/html;charset=utf-8");
        DiskFileItemFactory factory = new DiskFileItemFactory();
        File f = new File(req.getSession().getServletContext().getRealPath("tmp"));
        if (!f.exists()) {
            f.mkdirs();
        }
        factory.setRepository(f);
        ServletFileUpload fileupload = new ServletFileUpload(factory);
        fileupload.setHeaderEncoding("UTF-8");
        try {
            List<FileItem> fileItems = fileupload.parseRequest(req);
            for (FileItem fileItem : fileItems) {
                if (fileItem.isFormField()) {
                    String fieldName = fileItem.getFieldName();
                    String fieldvalue = fileItem.getString("utf-8");

                } else {
                    String name = fileItem.getName();
                    String extName = "." + FilenameUtils.getExtension(name);
                    for (String s : this.donExtList) {
                        if (s.equals(extName)) {

                            String fileUUIDName = UUID.randomUUID().toString();
                            String upFileName = fileUUIDName + extName;
                            String uploadPath = req.getSession().getServletContext().getRealPath("uploads/" + upFileName);
                            //创建文件
                            File file = new File(uploadPath);
                            file.getParentFile().mkdirs();
                            file.createNewFile();
                            //创建文件输出流
                            FileOutputStream out = new FileOutputStream(file);
                            //获取文件输入流
                            InputStream in = fileItem.getInputStream();
                            byte[] buffer = new byte[1024];
                            int len = -1;
                            while ((len = in.read(buffer)) != -1) {
                                out.write(buffer, 0, len);
                            }

                            in.close();
                            out.close();
                            //删除fileTtem对象
                            fileItem.delete();
                            PrintWriter writer = resp.getWriter();
                            writer.write("上传路径:" + uploadPath);
                            break;

                        }
                    }

                }
            }
        } catch (FileUploadException e) {
            throw new RuntimeException(e);

        }

    }
}

上传参数可控

package com.example;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

@MultipartConfig
@WebServlet("/upload2")
public class UploadServlet2 extends HttpServlet {

    private List<String> donExtList= Arrays.asList(".jspx",".jsp",".php",".java",".class");

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置编码集(解码)
        req.setCharacterEncoding("UTF-8");
        // 设置响应的编码集
        resp.setContentType("text/html;charset=utf-8");
        Part part = req.getPart("file");
        String fileUUIDName = UUID.randomUUID().toString();
        String realName = part.getSubmittedFileName();;
        String ext = realName.substring(realName.lastIndexOf("."));
        for (String s : this.donExtList) {
            if (s.equals(ext)) {
                resp.getWriter().write("禁止上传改类型文件");
                return;
            }
        }
        String path = null == req.getParameter("path")?"":req.getParameter("path");
        String ServletPath = req.getSession().getServletContext().getRealPath("uploads/");
        String uploadPath =ServletPath+ path+fileUUIDName+ext;
        resp.getWriter().write("上传成功:"+uploadPath);
        FileOutputStream stream = new FileOutputStream(uploadPath);
        IOUtils.copy(part.getInputStream(),stream);
    }

}

漏洞分析

String path = null == req.getParameter("path")?"":req.getParameter("path");
//参数可控
jar -cvf test.war cmd.jsp

CleanShot 2024-01-18 at 17.29.29.png
CleanShot 2024-01-18 at 17.30.14.png
CleanShot 2024-01-18 at 17.31.11.png

其他漏洞

除了以上三种常见漏洞 还有其他漏洞 例如 文件头检测 制作图片一句话就可以绕过。
文件类型检测 上传时候修改Content-Type: image/jpeg 类型就可以绕过。

5.防御建议

1.设置上传白名单 例如只允许上传 png gif jpg等图片类型后缀
2.不要使用客户端可控参数 需要使用时必须过滤。
3.上传目录 禁止解析脚本文件。

标签:审计,FileUpload,Java,String,req,new,import,servlet,javax
From: https://www.cnblogs.com/Gp3r/p/17994695

相关文章

  • javax.annotation.Nullable找不到
    您需要包括一个存在该类的罐子。您可以在这里找到它如果使用Maven,则可以添加以下依赖项声明:<dependency><groupId>com.google.code.findbugs</groupId><artifactId>jsr305</artifactId><version>3.0.2</version></dependency>对于Gradle:dependencies......
  • Windows下安装和配置Java JDK
    1、......
  • 配置java环境(Redhat)
    安装前准备:下载java的Jdkhttps://www.oracle.com/java/technologies/downloads/   (jdk-8u202-linux-x64.tar.gz)1.查看系统是否有java环境: java-version2.如果有版本输出则需要卸载之前的jdk,找出安装的jdk: rpm-qa|grepjdk3.根据 rpm-qa|grepjdk命令列出的......
  • JVM(Java虚拟机) 整理
    JVM整体结构本文主要说的是HotSpot虚拟机,JVM全称是JavaVirtualMachine,中文译名:Java虚拟机简化一下:Java字节码文件Class文件本质上是一个以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,JVM根据其特定的规则解析该二进制数据,从而得到......
  • java中二分查找前提必须是升序吗?
    二分查找不必须是升序,降序排列的数组也可以执行二分查找。二分查找算法是一种高效的搜索方法,它要求数据集是有序的,无论是升序还是降序都可以。在升序排列的情况下,算法会将目标值与中间值比较,如果目标值较小,则在左半部分继续查找;如果目标值较大,则在右半部分继续查找。在降序排列的......
  • 【干货】一文掌握JavaScript检查对象空值的N种技巧!
    在开发JavaScript应用程序时,经常需要检查对象是否为空。这是因为在处理和操作对象数据时,我们需要确保对象包含有效的值或属性。以下是一些常见情况,我们需要检查JavaScript对象是否为空:防止空引用错误:当我们尝试访问或使用一个空对象时,可能会导致空引用错误(如TypeError:Cann......
  • 【干货】一文掌握JavaScript检查对象空值的N种技巧!
    在开发JavaScript应用程序时,经常需要检查对象是否为空。这是因为在处理和操作对象数据时,我们需要确保对象包含有效的值或属性。以下是一些常见情况,我们需要检查JavaScript对象是否为空:防止空引用错误:当我们尝试访问或使用一个空对象时,可能会导致空引用错误(如TypeError:Can......
  • 深入浅出Java多线程(二):Java多线程类和接口
    引言大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第二篇内容:Java多线程类和接口。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!!在现代计算机系统中,多线程技术是提升程序性能、优化资源利用和实现并发处理的重要手段。特别是在Java编程语言中,多线程机......
  • java读取并解析XML文件的方法有哪些?
    XMLStreamReader:1. DOM(DocumentObjectModel)方式:DOM将整个XML文档加载到内存中,形成一颗树状结构,然后通过操作这个树状结构来获取所需要的数据。示例代码如下:importjavax.xml.parsers.*;importorg.w3c.dom.*;publicclassXMLParser{publicstaticvoidmain(Stri......
  • java冒泡排序的三种实现方法
    第一种通过简单的比较相邻的元素,如果他们的顺序是错误的,则交换它们的位置。重复这个步骤,直到没有更多要交换的元素为止。j变量代表未排序数组范围的右边界,j以后的已经排序publicstaticvoidbubble(int[]nums,intj){if(j==0){return;}......