一文吃透文件上传漏洞!路径遍历、后缀绕过、ZIP炸弹全解析

admin 2025年4月16日17:10:26评论46 views字数 5689阅读18分57秒阅读模式

🔍 文件上传漏洞Java源码审计详解(附代码分析)

文件上传是 Web 应用中极其常见的功能,但一旦实现不当,极易造成严重漏洞,如:上传 WebShell、任意文件写入、远程命令执行等。本篇将从源码审计角度,深入剖析文件上传中关键风险点,包含路径处理、文件大小限制、后缀校验、绕过技巧、白名单误用等,并提供典型实现方式与安全建议。

🧱 文件上传的常见实现方式

✅ 1. Spring MultipartFile 实现

@PostMapping("/upload")public String upload(@RequestParam("file") MultipartFile file) throws IOException {    String fileName = file.getOriginalFilename();    File dest = new File("/upload/dir/" + fileName);    file.transferTo(dest);return"上传成功";}

🔍 问题点分析:

点位
风险
fileName
用户可控,未清洗,可能包含 ../
拼接路径
易导致目录穿越
文件后缀
未校验,可能上传 .jsp 等恶意脚本
随机性
无随机命名,可能覆盖旧文件
文件大小
未限制,可能被恶意上传大文件、Zip炸弹等

⚠️ 2. 使用 Apache Commons FileUpload(ServletFileUpload)

DiskFileItemFactory factory = new DiskFileItemFactory();ServletFileUpload upload = new ServletFileUpload(factory);List<FileItem> items = upload.parseRequest(request);for (FileItem item : items) {if (!item.isFormField()) {        String fileName = item.getName();        File file = new File("/upload/" + fileName);        item.write(file);    }}

🔍 风险分析与绕过技巧:

  • item.getName() 可被伪造,返回值可能为:../../webapps/ROOT/shell.jsp
  • 若直接写入本地文件系统,无适当处理,可能形成 RCE。
  • Commons FileUpload 不自带后缀检查,全部靠开发人员自己处理

☠️ 核心攻击点分析

📁 1. 路径遍历(Path Traversal)

❌ 不安全代码:

String fileName = request.getParameter("fileName");File file = new File("/upload/" + fileName);

攻击示例:

fileName=../../../../webapps/ROOT/shell.jsp

🧨 效果: 上传的文件可能被写入 Web项目根目录 下,造成远程代码执行。

✅ 安全建议:

  • 禁止文件名中包含 ../、空格、%编码字符等。
  • 使用 file.getCanonicalPath() 与上传目录前缀比对。
File dest = new File(uploadDir, fileName);String canonicalPath = dest.getCanonicalPath();if (!canonicalPath.startsWith(uploadDir.getCanonicalPath())) {thrownew SecurityException("路径非法");  //会判断上传文件的目录是否在合法目录}

📏 2. 文件大小未限制(DoS 风险)

未设置上传大小限制,攻击者可构造超大文件导致内存/磁盘耗尽。

✅ Spring Boot 配置:

spring:servlet:multipart:max-file-size:10MBmax-request-size:20MB

💣 3.ZIP 炸弹攻击分析

ZIP 炸弹是一种特制的压缩文件:

  • 文件体积非常小(几十 KB)
  • 解压后数据极大(几个 GB 到 TB)
  • 常用于攻击文件上传和解压服务,使 CPU、内存或磁盘瞬间耗尽

📉 攻击形式举例:

压缩前
压缩后
压缩比
10 GB
20 KB
500,000:1

攻击者可能上传 20KB.zip,但解压后文件达到 10GB,导致:

  • 磁盘被写满
  • 内存耗尽或服务器卡死
  • 服务完全崩溃

🔒 ZIP 炸弹防护建议

✅ 服务端代码解压前,务必加上以下限制:
  1. 限制解压后文件总大小
  2. 限制单个文件大小
  3. 限制文件数量
  4. 检测压缩比(压缩比过高直接拒绝)

🧪 Java 安全解压 ZIP 示例(带限制)

import java.io.*;import java.util.zip.ZipEntry;import java.util.zip.ZipInputStream;publicclassSafeZipExtractor{privatestaticfinallong MAX_TOTAL_UNZIPPED_SIZE = 100 * 1024 * 1024// 100MBprivatestaticfinallong MAX_SINGLE_FILE_SIZE = 50 * 1024 * 1024;    // 50MBprivatestaticfinalint MAX_FILE_COUNT = 100;publicstaticvoidunzipSafely(File zipFile, File targetDir)throws IOException {long totalUnzippedSize = 0;int fileCount = 0;try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {            ZipEntry entry;while ((entry = zis.getNextEntry()) != null) {                fileCount++;if (fileCount > MAX_FILE_COUNT) {thrownew SecurityException("解压文件数过多,疑似 ZIP 炸弹");                }                File newFile = new File(targetDir, entry.getName()).getCanonicalFile();// 防止路径穿越if (!newFile.getPath().startsWith(targetDir.getCanonicalPath())) {thrownew SecurityException("非法路径,疑似穿越攻击: " + entry.getName());                }// 逐步写出解压文件try (FileOutputStream fos = new FileOutputStream(newFile)) {byte[] buffer = newbyte[4096];int len;long singleFileSize = 0;while ((len = zis.read(buffer)) > 0) {                        singleFileSize += len;                        totalUnzippedSize += len;if (singleFileSize > MAX_SINGLE_FILE_SIZE) {thrownew SecurityException("单个文件过大,疑似 ZIP 炸弹");                        }if (totalUnzippedSize > MAX_TOTAL_UNZIPPED_SIZE) {thrownew SecurityException("解压内容总体积过大,疑似 ZIP 炸弹");                        }                        fos.write(buffer, 0, len);                    }                }            }        }    }}

✅ 上面防护总结:

检查项
防护内容
路径合法性检查
防止 ZIP 路径穿越攻击
文件数上限
防止上传含成千上万小文件的压缩包
单文件大小限制
防止解压出超大单个文件
总解压体积限制
阻止总体积膨胀的 ZIP 炸弹

🚨 其他方式:

  • 使用像 Zip4j 或 Apache Commons Compress 这样的库能获得更多安全控制。

📂 4. 后缀名校验缺失或被绕过

❌ 错误做法:黑名单

if (fileName.endsWith(".jsp") || fileName.endsWith(".php")) {thrownew SecurityException("禁止上传脚本文件");}

🧨 绕过方式:

方法
示例
双扩展名绕过
shell.jpg.jsp
大小写绕过
shell.JSP
特殊符号绕过
shell.jsp%00.jpg

(早期漏洞)

✅ 正确做法:白名单校验

List<String> allowExt = Arrays.asList(".jpg"".png"".pdf"".docx");String ext = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();//.的定位也很重要,如果用的不是lastIndexOf定位最后一个.大概率存在问题if (!allowExt.contains(ext)) {thrownew SecurityException("非法文件类型");}

🎭 5. 文件名中“点”的位置及隐藏文件攻击

攻击者可上传如下文件名:

  • .htaccess
  • .env
  • .ssh/authorized_keys
  • abc.jsp. (带空格)

🧨 某些系统识别后缀可能失误,或将其当作隐藏文件,或绕过后缀判断。

✅ 建议:

  • 拒绝以点开头的文件(隐藏文件)
  • 拒绝多个点或尾部空格(如 file.jsp 
if (fileName.startsWith(".") || fileName.contains("..") || fileName.trim().endsWith(".")) {thrownew SecurityException("非法文件名");}

🔒 推荐安全上传实现

@PostMapping("/upload")public String secureUpload(@RequestParam("file") MultipartFile file) throws IOException {    String originalName = file.getOriginalFilename();// 后缀白名单    String suffix = originalName.substring(originalName.lastIndexOf(".")).toLowerCase();    List<String> allow = Arrays.asList(".jpg"".png"".pdf");if (!allow.contains(suffix)) {thrownew IllegalArgumentException("不允许的文件类型");    }// 随机命名 + 限定目录    String newName = UUID.randomUUID().toString().replace("-""") + suffix;    File saveDir = new File("/opt/upload/");if (!saveDir.exists()) saveDir.mkdirs();    File dest = new File(saveDir, newName);// 路径校验if (!dest.getCanonicalPath().startsWith(saveDir.getCanonicalPath())) {thrownew SecurityException("非法路径");    }    file.transferTo(dest);return"上传成功";}

🛡️ 审计 Checklist

检查项
是否必做
建议
文件名是否随机生成?
UUID 或雪花算法
后缀名是否白名单?
严格控制
是否防路径穿越?
canonicalPath 比对
是否限制大小?
配置或代码判断
是否隔离存储目录?
不与 webroot 混用
是否拦截隐藏文件?
.xxx

 拒绝上传

📌 文件上传总结

🧨 攻击点
🎯 攻击方式
🛡 防御建议
路径控制
- 路径穿越 ../- 绝对路径注入- 上传符号链接
- 使用 getCanonicalPath() 规范化路径- 校验目标路径是否在允许目录内- 禁止软链接上传
文件类型绕过
- 双扩展 .php.jpg- 特殊字符 .php%00.jpg- 控制字符/Unicode 后缀绕过- 魔术头伪装
- 使用魔术头/MIME 检测文件内容- 白名单方式验证扩展名- 禁止解析上传目录(如配置 nginx/php)
ZIP炸弹
- 高压缩比 ZIP- 多层嵌套压缩包- 超大文件数
- 限制最大解压大小、文件数、嵌套层数- 使用 Zip4j/Apache Commons 解压时封装限制- 拒绝可疑压缩比(如 >1000:1)
图片马
- 上传带 PHP 代码的图片- 利用图片解析漏洞
- 不允许上传至可执行目录- 拒绝上传含脚本内容的图像(验证内容头)
WAF绕过/编码绕过
- 双写编码绕过- Content-Type 伪造- 分块传输绕过检测
- 上传前验证 MIME 与扩展是否匹配- 服务端统一校验,不信任前端类型信息- 使用 RASP 或反向代理层识别异常流量
大文件/并发DoS
- 上传超大文件压垮服务器- 并发上传大量小文件耗尽 inode
- 限制 max-file-size / max-request-size- 控制上传频率(限流)- 后台作业处理上传文件
上传后可访问
- 上传后直接访问执行 shell- 前后端路径绕过
- 上传文件重命名为随机 UUID- 上传目录配置为不可执行- 存储层与访问层解耦

原文始发于微信公众号(季升安全):一文吃透文件上传漏洞!路径遍历、后缀绕过、ZIP炸弹全解析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月16日17:10:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一文吃透文件上传漏洞!路径遍历、后缀绕过、ZIP炸弹全解析https://cn-sec.com/archives/3965389.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息