当前位置: 首页 > news >正文

【Java代码审计 | 第七篇】文件上传漏洞成因及防范

未经许可,不得转载。

文章目录

    • 文件上传漏洞
    • 漏洞成因
      • 未验证文件类型和扩展名
      • 未限制文件上传路径
    • 防范
      • 验证文件类型和扩展名
      • 验证文件内容
      • 限制文件上传路径
      • 使用安全的文件上传库
    • 标准代码

在这里插入图片描述

文件上传漏洞

文件上传漏洞是指攻击者通过上传恶意文件(如可执行脚本、病毒、木马等)到服务器,从而执行恶意操作或获取服务器控制权的安全漏洞,一般发生在应用程序未对上传的文件进行严格的验证和限制时。

漏洞成因

未验证文件类型和扩展名

import java.io.File;
import java.io.IOException;
import java.util.List;
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 org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;@WebServlet("/upload") // 指定Servlet的URL映射
public class FileUploadServlet extends HttpServlet {// 处理POST请求,实现文件上传protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 检查请求是否为多部分(即文件上传请求)if (ServletFileUpload.isMultipartContent(request)) {// 创建磁盘文件项工厂,用于处理文件上传DiskFileItemFactory factory = new DiskFileItemFactory();ServletFileUpload upload = new ServletFileUpload(factory);try {// 解析请求,获取文件项列表List<FileItem> items = upload.parseRequest(request);for (FileItem item : items) {// 检查是否为文件字段,而不是普通表单字段if (!item.isFormField()) {// 获取上传文件的文件名String fileName = new File(item.getName()).getName();// 定义服务器上的文件存储路径(此处为/uploads/目录)String filePath = "/uploads/" + fileName;// 将上传的文件写入到服务器指定路径item.write(new File(filePath));// 向客户端返回上传成功的信息response.getWriter().println("File uploaded: " + fileName);}}} catch (Exception e) {e.printStackTrace(); // 打印异常信息,方便调试}}}
}

问题:未验证文件类型和扩展名,攻击者可以上传任意文件(如 .jsp.exe 等)。

未限制文件上传路径

String filePath = "/uploads/" + fileName;
item.write(new File(filePath));

问题:文件上传路径未做限制,攻击者可以通过构造特殊文件名(如 ../../malicious.jsp)将文件上传到任意目录。

防范

验证文件类型和扩展名

  • 使用白名单机制,只允许上传指定的文件类型(如 .jpg.png.pdf 等)。
  • 不要依赖客户端验证(如 HTML 的 accept 属性),必须在服务器端进行验证。
String[] allowedExtensions = { "jpg", "png", "pdf" };
String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();boolean isValidExtension = false; //默认为不合法类型
for (String ext : allowedExtensions) {if (ext.equals(fileExtension)) {isValidExtension = true; //若匹配到白名单,则合法。break;}
}if (!isValidExtension) {response.getWriter().println("Invalid file type.");return;
}

验证文件内容

使用工具(如 Apache Tika)验证文件内容是否与扩展名匹配。

import org.apache.tika.Tika;Tika tika = new Tika();
String detectedType = tika.detect(new File(filePath));if (!detectedType.startsWith("image/")) {response.getWriter().println("Invalid file content.");new File(filePath).delete(); // 删除非法文件return;
}

限制文件上传路径

将文件上传路径限制在特定目录,避免攻击者通过路径遍历上传文件到任意目录。

String uploadDir = "/uploads/";
String filePath = uploadDir + randomFileName;// 确保路径在允许的目录内
if (!filePath.startsWith(uploadDir)) {response.getWriter().println("Invalid file path.");return;
}

使用安全的文件上传库

使用经过验证的文件上传库(如 Apache Commons FileUpload),并确保库的版本是最新的。

标准代码

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.tika.Tika;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;
import java.util.UUID;@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MBprivate static final String UPLOAD_DIRECTORY = "/uploads"; // 上传目录private static final String[] ALLOWED_EXTENSIONS = { "jpg", "jpeg", "png", "pdf" }; // 允许的文件扩展名@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 检查是否为文件上传请求if (!ServletFileUpload.isMultipartContent(request)) {response.getWriter().println("Request does not contain upload data.");return;}// 配置上传参数DiskFileItemFactory factory = new DiskFileItemFactory();ServletFileUpload upload = new ServletFileUpload(factory);upload.setSizeMax(MAX_FILE_SIZE); // 设置最大文件大小try {// 解析请求List<FileItem> items = upload.parseRequest(request);for (FileItem item : items) {if (!item.isFormField()) {// 判断当前的 FileItem 是否是一个普通的表单字段,如果不是,则执行后续的文件上传处理逻辑。// 获取文件名String fileName = new File(item.getName()).getName();String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();// 验证文件扩展名if (!isAllowedExtension(fileExtension)) {response.getWriter().println("Invalid file type. Allowed types: " + String.join(", ", ALLOWED_EXTENSIONS));return;}// 生成随机文件名String randomFileName = UUID.randomUUID().toString() + "." + fileExtension;String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIRECTORY;File uploadDir = new File(uploadPath);// 创建上传目录(如果不存在)if (!uploadDir.exists()) {uploadDir.mkdir();}// 保存文件String filePath = uploadPath + File.separator + randomFileName;File storeFile = new File(filePath);item.write(storeFile);// 验证文件内容if (!isValidFileContent(storeFile, fileExtension)) {response.getWriter().println("Invalid file content.");storeFile.delete(); // 删除非法文件return;}response.getWriter().println("File uploaded successfully: " + randomFileName);}}} catch (Exception e) {response.getWriter().println("Error occurred: " + e.getMessage());}}/*** 检查文件扩展名是否合法*/private boolean isAllowedExtension(String fileExtension) {for (String ext : ALLOWED_EXTENSIONS) {if (ext.equalsIgnoreCase(fileExtension)) {return true;}}return false;}/*** 验证文件内容是否合法*/private boolean isValidFileContent(File file, String expectedExtension) throws IOException {Tika tika = new Tika();String detectedType = tika.detect(file);// 根据文件扩展名验证内容类型switch (expectedExtension.toLowerCase()) {case "jpg":case "jpeg":return detectedType.equals("image/jpeg");case "png":return detectedType.equals("image/png");case "pdf":return detectedType.equals("application/pdf");default:return false;}}
}

http://www.mrgr.cn/news/93682.html

相关文章:

  • 本地部署大语言模型-DeepSeek
  • 【Java代码审计 | 第四篇】SQL注入防范
  • 根据输入汉字生成带拼音的米字格字帖
  • Hive八股
  • SQL经典查询
  • 存量思维和增量思维
  • 项目实战--网页五子棋(对战功能)(9)
  • Scala 中生成一个RDD的方法
  • LeetCodeHot100
  • 【落羽的落羽 C++】C++入门基础:引用,内联,nullptr
  • c语言笔记 一维数组与二维数组
  • 【Tools】Windows下Git 2.48安装教程详解
  • [数据抓取] Python 网络爬虫 - 学习手册
  • 硬件基础(4):(1)AD采集电路设计
  • 使用express创建服务器保存数据到mysql
  • 【Linux】权限相关知识点
  • GPU编程实战指南01:CUDA编程极简手册
  • P6412题解
  • 前端快速搭建Node服务(解决跨域问题)
  • HCIA复习拓扑实验