string.endswith() 不能安全校验文件扩展名,因不区分大小写、无法识别恶意多扩展名(如"malicious.jpg.exe")且易受路径中点号干扰;须先用lastindexof(".")提取真实扩展名并转小写,再白名单校验。

String.endsWith() 本身不能安全校验文件扩展名
直接用 String.endsWith() 判断扩展名(比如 filename.endsWith(".jpg"))看似简单,但极易出错:它不区分大小写、不验证扩展名是否在文件名末尾真实存在("malicious.jpg.exe" 也会通过 .endsWith(".jpg")),更无法处理路径中含点号的场景(如 "/home/user/my.file.txt")。浏览器上传的 File.name 或后端收到的原始文件名,都可能被恶意构造或包含多层路径信息。
必须先提取真实扩展名再比对
核心是「分离扩展名」——不是字符串后缀匹配,而是按最后一个 . 拆分,并确保该 . 出现在合法位置(即不在开头、不连续、且后面还有字符)。推荐做法:
- 用
lastIndexOf(".")找最后一个点的位置 - 检查该位置是否 > 0(排除
".gitignore"这类隐藏文件误判)且lastIndex != filename.length - 1(排除以点结尾) - 截取
filename.substring(lastIndex + 1)得到小写扩展名(如"JPG"→"jpg") - 用
Set.of("jpg", "png", "pdf").contains(ext)或Arrays.asList(...).contains()做白名单校验
示例片段:
String ext = "";
int dotIndex = filename.lastIndexOf(".");
if (dotIndex > 0 && dotIndex <h3>注意前端校验仅作辅助,后端必须重复验证</h3><p>前端用 <code>String.endsWith()</code> 做即时提示(如“仅支持 JPG/PNG”)可以提升体验,但完全不可信。攻击者可绕过 JS 直接发请求;浏览器也可能修改 <code>File.name</code>(如 Safari 对中文路径的处理)。后端收到的原始文件名、Content-Type、甚至文件魔数(magic bytes)都应参与判断。尤其当使用 <code>MultipartFile.getOriginalFilename()</code>(Spring)或 <code>req.files?.file?.name</code>(Express)时,必须重新提取扩展名——不能依赖前端传来的 <code>ext</code> 字段。</p><h3>Node.js 和 Java 中的典型陷阱</h3><p>Node.js 的 <code>path.extname()</code> 看似方便,但它会返回带点的扩展名(如 <code>".jpg"</code>),直接用于 <code>Set.contains()</code> 会失败;Java 的 <code>FilenameUtils.getExtension()</code>(Apache Commons)默认不转小写,且对 <code>"archive.tar.gz"</code> 返回 <code>"gz"</code> 而非 <code>"tar.gz"</code>。这些细节都会导致白名单失效。最稳妥的方式仍是手动用 <code>lastIndexOf(".") + substring()</code> 提取纯扩展名字符串,并统一转小写后再比对。</p><p>真正关键的不是“怎么调用 <code>endsWith</code>”,而是“怎么拿到那个值得比对的扩展名字符串”。漏掉路径清理、大小写归一或边界检查,再短的代码也等于没校验。</p>










