java基础漏洞学习----文件操作漏洞

发布时间 2023-10-20 23:26:20作者: BattleofZhongDinghe

java基础漏洞学习----文件操作漏洞

前置基础知识

https://www.cnblogs.com/thebeastofwar/p/17760812.html

文件上传漏洞

文件上传的方式

1.通过文件流
index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
<form action="uploadFile1" method="post" enctype="multipart/form-data">
    <input type="file" name="file" /><br/>
    <input type="submit" value="上传" />
</form>
</body>
</html>

UploadFileServlet1.java

package com.example.servletdemo;//根据自己的项目名称更改

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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;

@WebServlet("/uploadFile1")
@MultipartConfig(fileSizeThreshold = 1024 * 1024 * 2, // 2MB
        maxFileSize = 1024 * 1024 * 10, // 10MB
        maxRequestSize = 1024 * 1024 * 50) // 50MB
public class UploadFileServlet1 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        String savePath = "C:/uploads"; // 上传文件保存的目录
        File fileSaveDir = new File(savePath);
        if (!fileSaveDir.exists()) {
            fileSaveDir.mkdirs();
        }

        for (Part part : request.getParts()) {
            String fileName = extractFileName(part);
            fileName = new String(fileName.getBytes("ISO-8859-1"), "UTF-8");
            InputStream inputStream = part.getInputStream();
            OutputStream outputStream = new FileOutputStream(savePath + File.separator + fileName);
            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
            outputStream.close();
            inputStream.close();
        }

        response.getWriter().println("文件上传成功!");
    }

    private String extractFileName(Part part) {
        String contentDisposition = part.getHeader("content-disposition");
        String[] items = contentDisposition.split(";");
        for (String item : items) {
            if (item.trim().startsWith("filename")) {
                return item.substring(item.indexOf("=") + 2, item.length() - 1);
            }
        }
        return "";
    }
}

2.通过ServletFileUpload
改maven源
在setting里搜索maven

然后在那个目录中新建一个settings.xml

内容,然后点击override

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                          https://maven.apache.org/xsd/settings-1.0.0.xsd">
      
      <mirrors>
    	<mirror>  
      		<id>huaweimaven</id>  
      		<name>huaweimaven</name>  
      		<url>https://repo.huaweicloud.com/repository/maven/</url>  
      		<mirrorOf>central</mirrorOf>          
    	</mirror>  
      </mirrors>
</settings>

然后再pom.xml中alt+insert快捷键添加commons-io和commons-fileupload依赖

主代码

package com.example.servletdemo;

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 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.File;
import java.io.IOException;
import java.util.List;

@WebServlet("/uploadFile2")
public class UploadFileServlet2 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        // 检查是否为文件上传请求
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (!isMultipart) {
            response.getWriter().println("请选择文件");
            return;
        }

        // 创建文件上传处理工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();

        // 设置内存缓冲区大小,超过大小的文件将被保存到临时文件目录
        factory.setSizeThreshold(1024 * 1024); // 1MB

        // 设置临时文件目录
        File tempDir = new File("C:/temp");
        factory.setRepository(tempDir);

        // 创建文件上传处理器
        ServletFileUpload upload = new ServletFileUpload(factory);

        // 设置请求的最大文件大小
        upload.setSizeMax(1024 * 1024 * 10); // 10MB

        try {
            // 解析文件上传请求
            List<FileItem> items = upload.parseRequest(request);

            for (FileItem item : items) {
                // 检查是否为普通表单字段
                if (item.isFormField()) {
                    // 处理普通表单字段
                    String fieldName = item.getFieldName();
                    String fieldValue = item.getString("UTF-8");
                    // TODO: 处理普通表单字段的值
                } else {
                    // 处理文件字段
                    String fieldName = item.getFieldName();
                    String fileName = item.getName();
                    // TODO: 处理文件字段的值
                    File uploadedFile = new File("C:/uploads/" + fileName);
                    item.write(uploadedFile);
                }
            }

            response.getWriter().println("文件上传成功!");
        } catch (FileUploadException e) {
            e.printStackTrace();
            response.getWriter().println("文件上传失败!");
        } catch (Exception e) {
            e.printStackTrace();
            response.getWriter().println("文件上传失败!");
        }
    }
}

3.通过MultipartFile(失败,不知为什么总报404)
在pom.xml中添加Sprintboot MVC相关依赖

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.6.3</version>
            </dependency>
        </dependencies>

主代码

package com.example.servletdemo;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;

public class UploadFileServlet3 {

    @PostMapping("/uploadFile3")
    public String uploadFile(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return "请选择文件";
        }

        String fileName = file.getOriginalFilename();
        String filePath = "C:/uploads/";

        try {
            File dest = new File(filePath + fileName);
            file.transferTo(dest);
            return "文件上传成功";
        } catch (IOException e) {
            e.printStackTrace();
            return "文件上传失败";
        }
    }
}

常见java的文件上传漏洞的代码

1.filename.indexof与filename.lastIndexof
shell.png.jsp
如果是filename.indexof获取的就是.png.jsp(即从第一个点获取)
如果是filename.lastIndexof获取的就是.jsp(即从最后一个点获取)
上传webshell测试代码,上传helloworld.png.jsp和shell.png.jsp
index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
<form action="uploadFile4" method="post" enctype="multipart/form-data">
    <input type="file" name="file" /><br/>
    <input type="submit" value="上传" />
</form>
</body>
</html>

主代码

package com.example.servletdemo;

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 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.File;
import java.io.IOException;
import java.util.List;

@WebServlet("/uploadFile4")
public class UploadFileServlet4 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        // 检查是否为文件上传请求
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (!isMultipart) {
            response.getWriter().println("请选择文件");
            return;
        }

        // 创建文件上传处理工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();

        // 设置内存缓冲区大小,超过大小的文件将被保存到临时文件目录
        factory.setSizeThreshold(1024 * 1024); // 1MB

        // 设置临时文件目录
        File tempDir = new File("C:/temp");
        factory.setRepository(tempDir);

        // 创建文件上传处理器
        ServletFileUpload upload = new ServletFileUpload(factory);

        // 设置请求的最大文件大小
        upload.setSizeMax(1024 * 1024 * 10); // 10MB

        try {
            // 解析文件上传请求
            List<FileItem> items = upload.parseRequest(request);

            for (FileItem item : items) {
                // 检查是否为普通表单字段
                if (item.isFormField()) {
                    // 处理普通表单字段
                    String fieldName = item.getFieldName();
                    String fieldValue = item.getString("UTF-8");
                    // TODO: 处理普通表单字段的值
                } else {
                    // 处理文件字段
                    String fieldName = item.getFieldName();
                    String fileName = item.getName();
                    // 获取文件后缀
                    String fileExtension = fileName.substring(fileName.indexOf(".") + 1);//安全做法 fileName.lastIndexof(".")+1

                    /*
                    // 判断后缀是否属于指定的白名单
                    String[] whiteExtensions = {"png", "jpg", "gif"};
                    boolean isValidExtension = false;
                    for (String extension : whiteExtensions) {
                        if (fileExtension.equalsIgnoreCase(extension)) {
                            isValidExtension = true;
                            break;
                        }
                    }
                    if (!isValidExtension) {
                        response.getWriter().println("文件格式不支持(白名单)");
                        return;
                    }
                    */


                    //判断是否属于黑名单
                    String[] blackExtensions = {"jsp","jspx"};
                    for (String extension : blackExtensions) {
                        if (fileExtension.equalsIgnoreCase(extension)) {
                            response.getWriter().println("文件格式不支持(黑名单)");
                            return;
                        }
                    }


                        // TODO: 处理文件字段的值
                        File uploadedFile = new File(getServletContext().getRealPath("/") + "uploads/" + fileName);
                        item.write(uploadedFile);

                       //重定向到上传文件的位置 这样上传jsp文件就能解析了
                       String contextPath = request.getContextPath();
                       String jspPath = contextPath + "/uploads/" + fileName;
                       response.sendRedirect(jspPath);
                    }

                }
            response.getWriter().println("文件上传成功!");
        } catch (FileUploadException e) {
            e.printStackTrace();
            response.getWriter().println("文件上传失败!");
        } catch (Exception e) {
            e.printStackTrace();
            response.getWriter().println("文件上传失败!");
        }
    }
}


然后上传哥斯拉生成的jsp木马,哥斯拉连接,初始目录不是上传webshell的目录,而是tomcat的bin目录

2.前端校验
index2.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
    <script>
        function checkFileType() {
            var fileInput = document.getElementById("file");
            var file = fileInput.files[0];
            var fileType = file.type;
            var allowedTypes = ["image/png", "image/jpeg", "image/gif"];
            
            if (!allowedTypes.includes(fileType)) {
                alert("请选择图片文件(png, jpg, gif)");
                return false;
            }
            
            return true;
        }
    </script>
</head>
<body>
    <form action="uploadFile4" method="post" enctype="multipart/form-data" onsubmit="return checkFileType()">
        <input type="file" name="file" id="file" /><br/>
        <input type="submit" value="上传" />
    </form>
</body>
</html>

上传图片马然后修改文件后缀就可以了

3.MIME检测

package com.example.servletdemo;

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 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.File;
import java.io.IOException;
import java.util.List;

@WebServlet("/uploadFile5")
public class UploadFileServlet5 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        // 检查是否为文件上传请求
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (!isMultipart) {
            response.getWriter().println("请选择文件");
            return;
        }

        // 创建文件上传处理工厂
        DiskFileItemFactory factory = new DiskFileItemFactory();

        // 设置内存缓冲区大小,超过大小的文件将被保存到临时文件目录
        factory.setSizeThreshold(1024 * 1024); // 1MB

        // 设置临时文件目录
        File tempDir = new File("C:/temp");
        factory.setRepository(tempDir);

        // 创建文件上传处理器
        ServletFileUpload upload = new ServletFileUpload(factory);

        // 设置请求的最大文件大小
        upload.setSizeMax(1024 * 1024 * 10); // 10MB

        try {
            // 解析文件上传请求
            List<FileItem> items = upload.parseRequest(request);

            for (FileItem item : items) {
                // 检查是否为普通表单字段
                if (item.isFormField()) {
                    // 处理普通表单字段
                    String fieldName = item.getFieldName();
                    String fieldValue = item.getString("UTF-8");
                    // TODO: 处理普通表单字段的值
                } else {
                    // 处理文件字段
                    String fieldName = item.getFieldName();
                    String fileName = item.getName();
                    // 获取文件后缀
                    String fileExtension = fileName.substring(fileName.indexOf(".") + 1);//安全做法 fileName.lastIndexof(".")+1

                    //判断文件类型
                    String contentType = item.getContentType();
                    if (contentType.equals("image/png") || contentType.equals("image/jpeg") || contentType.equals("image/gif") || contentType.equals("application/pdf")) {

                        // TODO: 处理文件字段的值
                        File uploadedFile = new File(getServletContext().getRealPath("/") + "uploads/" + fileName);
                        item.write(uploadedFile);

                        //重定向到上传文件的位置 这样上传jsp文件就能解析了
                        String contextPath = request.getContextPath();
                        String jspPath = contextPath + "/uploads/" + fileName;
                        response.sendRedirect(jspPath);

                        response.getWriter().println("文件上传成功!");
                    }
                    else
                    {
                        response.getWriter().println("文件格式不支持(MIME)");
                    }
                }

            }

        } catch (FileUploadException e) {
            e.printStackTrace();
            response.getWriter().println("文件上传失败!");
        } catch (Exception e) {
            e.printStackTrace();
            response.getWriter().println("文件上传失败!");
        }
    }
}

Content-Type修改为image/png等白名单就可以了
4.头部检测
5.内容检测(JSP免杀)
检测常见恶意代码Runtime.getRuntime().exec

代码审计技巧

1.搜索org.apache.commons.fileupload java.ioFile
2.搜索.indexOf(
3.搜索MultipartFile,RequestMethod,MultipartHttpServletRequest,CommonsMutipartResolver等

目录遍历漏洞(任意文件读取&下载&删除)

package com.example.servletdemo;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FileContentServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String filename = request.getParameter("filename"); // 获取GET参数中的文件名
        String filePath = System.getProperty("user.dir") + "/" + filename; // 拼接当前工作目录和文件名

        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();

        try {
            FileReader fileReader = new FileReader(filePath);
            BufferedReader bufferedReader = new BufferedReader(fileReader);

            String line;
            while ((line = bufferedReader.readLine()) != null) {
                out.println(line);
            }

            bufferedReader.close();
        } catch (IOException e) {
            e.printStackTrace();
            out.println("Error: " + e.getMessage());
        }
    }
}

然后再web.xml中添加

    <servlet>
        <servlet-name>FileContentServlet</servlet-name>
        <servlet-class>com.example.servletdemo.FileContentServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>FileContentServlet</servlet-name>
        <url-pattern>/file</url-pattern>
    </servlet-mapping>


查看源码

zip 自解压

zip解压后的文件为恶意文件

package com.example.servletdemo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
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;

@WebServlet("/uploadFile6")
@MultipartConfig
public class UploadFileServlet6 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        String uploadPath = getServletContext().getRealPath("/") + "uploads"; // 获取项目根目录并指定上传文件保存的目录

        // 创建上传文件保存的目录(如果不存在)
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }

        // 获取上传的文件
        Part filePart = request.getPart("file");
        String fileName = getFileName(filePart);

        // 保存上传的文件到指定目录
        String filePath = uploadPath + File.separator + fileName;
        try (InputStream inputStream = filePart.getInputStream();
             FileOutputStream outputStream = new FileOutputStream(filePath)) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
        }

        // 解压上传的zip文件
        String unzipPath = getServletContext().getRealPath("/") + "unzips"; // 获取项目根目录并指定解压目录

        // 创建解压目录(如果不存在)
        File unzipDir = new File(unzipPath);
        if (!unzipDir.exists()) {
            unzipDir.mkdirs();
        }

        unzip(filePath, unzipPath);

        // 检查解压后的文件数量 如果数量为1 则跳转到解压文件位置 这样上传shell就可以解析了
        File[] unzippedFiles = unzipDir.listFiles();
        if (unzippedFiles != null && unzippedFiles.length == 1 && unzippedFiles[0].isFile()) {
            String contextPath = request.getContextPath();
            String jspPath = contextPath + "/unzips/" + unzippedFiles[0].getName();
            response.sendRedirect(jspPath);
        } else {
            response.getWriter().println("文件上传和解压成功!");
        }
    }

    // 获取上传文件的名称
    private String getFileName(Part part) {
        String contentDisposition = part.getHeader("content-disposition");
        String[] elements = contentDisposition.split(";");
        for (String element : elements) {
            if (element.trim().startsWith("filename")) {
                return element.substring(element.indexOf('=') + 1).trim().replace("\"", "");
            }
        }
        return null;
    }

    // 解压zip文件
    private void unzip(String zipFilePath, String destDirectory) throws IOException {
        try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath))) {
            byte[] buffer = new byte[4096];
            ZipEntry zipEntry = zipInputStream.getNextEntry();
            while (zipEntry != null) {
                String entryFileName = zipEntry.getName();
                String entryPath = destDirectory + File.separator + entryFileName;
                if (zipEntry.isDirectory()) {
                    File dir = new File(entryPath);
                    dir.mkdirs();
                } else {
                    try (FileOutputStream outputStream = new FileOutputStream(entryPath)) {
                        int bytesRead;
                        while ((bytesRead = zipInputStream.read(buffer)) != -1) {
                            outputStream.write(buffer, 0, bytesRead);
                        }
                    }
                }
                zipEntry = zipInputStream.getNextEntry();
            }
        }
    }
}