input[type="file"]的name不可靠,因其由客户端控制且可被篡改;File.type也可能为空或错误;前端仅能弱验证,真实文件类型必须由服务端通过Magic Number解析二进制头确定。

为什么 input[type="file"] 的 name 不可靠
用户选中文件后,input.files[0].name 返回的是浏览器提供的原始文件名,但这个值完全由客户端控制,可被任意篡改(比如把 evil.exe 改成 report.pdf)。仅靠它判断扩展名等于信任不可信输入,服务端校验前绝不能当作真实依据。
前端只能做弱验证:用 type 和 name 做初步过滤
File.type 是浏览器根据文件头和扩展名推测的 MIME 类型,但空文件、无扩展名或伪造文件会返回空字符串或错误类型(如 "" 或 "application/octet-stream");name 仍需提取扩展名并转小写比对,但仅用于 UX 提示或前置拦截。
- 提取扩展名用
name.split(".").pop().toLowerCase(),注意处理无点号情况(如README) - 检查
type是否在白名单内(如"image/jpeg"),但必须意识到它可能缺失或不准 - 不要用
name的扩展名做任何安全决策,比如“只允许 .jpg 就放行”
真实扩展名必须由服务端解析文件头(Magic Number)
唯一可靠的方式是服务端读取文件前若干字节,匹配 Magic Number。例如 JPEG 文件开头是 FF D8 FF,PNG 是 89 50 4E 47。前端无法访问原始二进制头(受限于 Blob API 和同源策略),所以这步无法绕过。
- Node.js 可用
file-type库解析Buffer - Python 推荐
python-magic或内置imghdr(有限支持) - PHP 用
finfo_open(FILEINFO_MIME_TYPE)比$_FILES["file"]["type"]可靠得多 - 切记:即使服务端识别出是图片,也要重命名文件(如生成 UUID),避免原名带路径遍历或执行风险
上传时如何让服务端拿到足够信息
前端不需要、也不应该试图“传真实扩展名”,而是确保服务端能拿到完整原始字节。常见错误是用 FormData.append("file", file) 后又额外传一个 ext 字段——这毫无意义,反而增加攻击面。
立即学习“前端免费学习笔记(深入)”;
- 只传
File对象本身,让服务端从二进制流解析 - 如果需要前端提示(如“不支持该格式”),可用
type+ 扩展名双校验,但提示语要写成“可能不支持”,而非“已拒绝” - 大文件上传时,部分服务端框架(如 Express)默认限制
req.file.buffer大小,需提前配置limits: { fileSize: ... }
真正决定文件类型的不是后缀名,是那几十个字节的二进制签名。所有绕过服务端解析的方案,本质上都是在赌用户不会故意传个伪装的恶意文件——而生产环境里,有人一定会赌。



















