accept属性不能拦住非法文件,仅影响前端文件选择器筛选;必须MIME类型与扩展名双写且无空格,如"application/pdf,.pdf";JS校验需结合文件名、type及文件头;后端必须解析二进制流校验真实MIME和扩展名。

accept 属性到底能不能拦住非法文件
不能。它只影响文件选择器的默认筛选,不提供任何安全防护。用户点开“所有文件”、拖拽、粘贴、甚至用 curl 直传,都能绕过 accept。常见错误是写了 accept="image/*" 就以为后端收到的一定是图片,结果上传了个 shell.php 改名成 avatar.jpg,直接触发 RCE。
accept 值怎么写才在 Chrome/Safari/iOS 上都生效
必须 MIME 类型 + 扩展名双写,且逗号后**不能有空格**。浏览器实现差异太大:Chrome 对 .pdf 敏感,Safari 更认 application/pdf,iOS Safari 则可能两个都忽略,除非显式列出。
-
accept="application/pdf,.pdf"✅(PDF 最小可靠组合) -
accept="application/vnd.openxmlformats-officedocument.wordprocessingml.document,.docx"✅(.docx 必须带 MIME) -
accept="image/jpeg,image/png,image/webp,.jpg,.png,.webp"✅(image/*在 Chrome 下会卡顿,在 iOS 可能漏 WebP) -
accept="text/plain,.txt,.md"✅(避免text/*匹配到text/html) -
accept="image/*,.png"❌(Safari 可能丢掉.png) -
accept=".pdf, .docx"❌(空格导致 Safari 忽略第二项)
前端 JS 校验该信 file.type 还是 file.name
都不全信。两者都可被伪造:file.type 在重命名后可能返回错误类型(如把 malware.exe 改名成 report.jpg,file.type 可能变成 image/jpeg);file.name 本身也可被构造。真正可靠的轻量校验是结合二者 + 文件头(magic bytes)。
- 先用
file.name.toLowerCase().endsWith('.pdf')快速过滤明显违规扩展名 - 再用
FileReader读取前 4 字节:new Uint8Array(await file.slice(0, 4).arrayBuffer()) - 比对 PDF 固定头
[0x25, 0x50, 0x44, 0x46](即%PDF),PNG 头[0x89, 0x50, 0x4E, 0x47] - 校验失败时,立刻清空
input.value = ""并提示,不要直接submit()
后端校验为什么不能只看 Content-Type 请求头
因为这个头完全由前端控制,curl -H "Content-Type: image/png" ... 就能伪造。真实校验必须解析文件二进制流:
立即学习“前端免费学习笔记(深入)”;
- 用服务端库(如 Node.js 的
file-type、Python 的python-magic)读取文件头,获取真实 MIME - 检查扩展名是否在白名单中,且与真实 MIME 逻辑一致(例如
.jpg应对应image/jpeg,而非text/plain) - 对
.html、.js、.zip等高危类型,即使 MIME 正确也应拒绝或重命名隔离 - 保存时强制重命名,剥离原始扩展名,由服务端根据检测结果附加安全后缀(如
upload_abc123.pdf)
最常被忽略的一点:自定义上传组件(如 Ant Design、Element Plus)往往没把 accept 透传给底层原生 <input type="file"> —— 用 DevTools 检查 DOM,确认渲染出的 input 元素上真有 accept 属性,否则前面所有配置都是空谈。



















