endsWith()不能用于安全白名单校验,因其仅匹配后缀、不解析真实扩展名,易被路径伪造、大小写混淆、多余点号等绕过;必须先规范提取并小写转换扩展名,再用Set精确比对。
直接用 string.endswith() 校验文件扩展名存在严重安全隐患,**不能单独使用它来做安全白名单校验**。它只能作为初步筛选的辅助手段,必须配合更严格的解析和比对逻辑。
为什么 endsWith() 本身不安全?
它只检查字符串末尾是否匹配,完全不解析真实扩展名,攻击者很容易绕过:
-
伪造路径干扰:上传文件名为
"evil.jpg..\webshell.jsp","webshell.jsp".endsWith(".jsp")返回true,但实际服务器可能按.jpg处理或因路径遍历触发漏洞; -
大小写混淆:
"SCRIPT.JS".endsWith(".js")是false,但很多 Web 服务器对扩展名不区分大小写; -
多余点号或空格:如
"safe.txt."、"data.json ",endsWith(".json")会失败,但某些环境仍可能执行; -
无扩展名或点号开头:如
".htaccess"或"Dockerfile",单纯靠endsWith容易漏判或误判。
正确做法:先提取真实扩展名,再比对白名单
核心步骤是「规范解析 + 严格比对」,而不是字符串后缀匹配:
- 从原始文件名中**提取最后一段有效扩展名**(去掉路径、处理多点、忽略末尾空白);
- 将提取结果**统一转为小写**(避免大小写绕过);
- 用
Set.of(".jpg", ".png", ".pdf").contains(ext)等方式做精确匹配; - 对空扩展名或非法格式(如只有
.)明确拒绝。
示例代码(Java):
public static boolean isValidExtension(String filename, Set<String> whitelist) {
if (filename == null || filename.trim().isEmpty()) return false;
<pre class="brush:php;toolbar:false;">// 提取基础文件名(去掉路径)
String basename = new File(filename).getName();
// 去除首尾空白
basename = basename.trim();
// 找最后一个 '.' 的位置,且不能是开头或结尾的 '.'
int lastDot = basename.lastIndexOf('.');
if (lastDot <= 0 || lastDot == basename.length() - 1) return false;
String ext = basename.substring(lastDot).toLowerCase(); // 包含 '.'
return whitelist.contains(ext);}
// 使用
Set
额外建议:增强鲁棒性
仅靠扩展名校验仍不够,生产环境应叠加以下措施:
-
服务端 MIME 类型检测:用
Tika或Files.probeContentType()检查实际内容类型,防止伪造扩展名; -
重命名上传文件:丢弃原始文件名,生成随机名 + 白名单内固定扩展名(如
abc123.png); -
限制上传目录权限:确保 Web 服务器无法执行该目录下的脚本文件(如禁用
php_flag engine off或设置noexec挂载选项); -
前端校验仅作提示:JS 的
endsWith()可用于用户体验优化,但绝不可替代后端验证。
小结:endsWith() 的合理定位
它适合用在「快速预过滤」或「日志/调试输出」场景,比如:
- 批量扫描时跳过明显不匹配的文件(提升性能);
- 打印日志时简单判断“看起来像图片”;
- 配合正则或
PathAPI 做二次确认前的轻量筛选。
但只要涉及安全控制,就必须走完整、健壮的扩展名解析 + 白名单比对流程。安全不是“看起来对”,而是“逻辑上无歧义、实现上无死角”。

















