accept属性仅前端提示,无法阻止非法文件上传;真正有效的文件类型限制必须由后端通过魔术字节、文件名清洗和安全存储路径三重校验实现。

accept 属性只能提示类型,不能阻止用户绕过 —— 真正的文件类型限制必须由后端校验决定。
为什么 accept 限制经常失效
浏览器只用它过滤文件选择对话框的默认视图,用户仍可手动切换“所有文件”并选中任意扩展名。比如设置 accept=".pdf" 后,用户在 Chrome 中点右上角下拉菜单选“所有文件”,就能上传 .exe。
- Firefox 和 Safari 对
accept的 MIME 类型支持更弱,accept="application/pdf"可能完全不生效 -
accept不校验文件内容,一个被重命名的恶意脚本(如report.pdf.js)照样能通过前端检查 - 移动端 WebView(如微信内置浏览器)常忽略
accept,尤其在 iOS 上
accept 的正确写法与常见组合
优先使用扩展名(兼容性最好),辅以 MIME 类型(供支持的浏览器增强提示)。多个值用逗号分隔,不要空格。
- 图片:
accept=".jpg,.jpeg,.png,.gif,.webp"或更稳妥的accept="image/jpeg,image/png,image/gif,image/webp" - PDF + Word:
accept=".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" - 音频:
accept=".mp3,.wav,.ogg,audio/mpeg,audio/wav,audio/ogg" - 禁止 HTML/JS:不要写
accept="text/html"—— 这反而会鼓励用户选 HTML 文件;应靠后端拦截
前端 JS 校验必须检查 file.type 和 file.name 两个字段
file.type 来自浏览器根据文件头或扩展名推测的 MIME,不可信但可作第一道筛;file.name 提供原始扩展名,用于白名单比对。
- 不要只信
file.type:一个伪造的.jpg文件可能报出""(空字符串)或text/plain - 提取扩展名要用
file.name.split('.').pop().toLowerCase(),注意处理无扩展名或带多个点的文件(如archive.tar.gz) - 推荐白名单校验逻辑:
const ext = getExtension(file); if (!['pdf', 'docx'].includes(ext)) { alert('仅支持 PDF 和 DOCX'); input.value = ''; } - 大小校验必须同步做:
if (file.size > 10 * 1024 * 1024) { /* 超过 10MB */ }
后端才是真正的守门人,且必须做三件事
前端一切限制都可被跳过,后端校验不是“补充”,而是唯一有效防线。
- 用魔术字节(magic bytes)识别真实文件类型,例如 PDF 开头是
%PDF,PNG 是89 50 4E 47—— 不能只看扩展名或Content-Type请求头 - 文件名必须用
secure_filename()(Flask)、path.basename()+ 正则清洗(Node.js)等函数重命名,剔除路径遍历字符(../)、控制符、Unicode 零宽空格等 - 存储路径绝不能在 Web 根目录下,例如上传到
/var/uploads/而非/var/www/html/uploads/,避免用户直接访问执行恶意文件
最容易被忽略的是:即使你用了 accept、写了 JS 校验、也做了后端 MIME 检查,只要没校验魔术字节,攻击者就能把 PHP shell 改个扩展名当成图片上传成功。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











