文件上传场景前端需要注意哪些安全问题?
简化版
文件上传是高风险入口。前端可以做文件类型、大小、数量、扩展名、预览和交互限制,但不能作为安全边界,因为攻击者可以绕过页面直接请求接口。真正安全必须由服务端校验 MIME、文件头、大小、病毒扫描、存储隔离、访问权限和文件名处理。前端重点是减少误传、避免预览 XSS、不要信任文件名和客户端 MIME。
详细版
前端上传校验提升体验,服务端校验保证安全。
| 风险 | 前端措施 | 服务端必须做 |
|---|---|---|
| 超大文件 | 限制 size | 限制请求体和存储 |
| 伪造类型 | accept + MIME 提示 | 检查文件头 |
| 恶意 SVG/HTML | 安全预览 | 转存、净化或禁止 |
| 文件名注入 | 展示转义 | 重命名和路径隔离 |
<input type="file" accept="image/png,image/jpeg" />
文件上传要记住:前端校验是用户体验,服务端校验才是安全边界。
完整版教学
一、为什么上传危险
文件上传让用户把任意内容交给系统。攻击者可能上传脚本、超大文件、伪造图片、压缩炸弹、带宏文档、恶意 SVG 或构造特殊文件名。
如果系统把上传文件当静态资源直接执行或预览,风险会放大。
二、前端能做什么
前端适合做体验型拦截:
- 限制文件大小。
- 限制文件数量。
- 提示允许类型。
- 上传前预览。
- 进度和取消。
- 分片上传和失败重试。
function validate(file: File) {
if (file.size > 5 * 1024 * 1024) throw new Error('文件过大')
if (!['image/png', 'image/jpeg'].includes(file.type)) throw new Error('类型不支持')
}
三、为什么前端校验不可信
攻击者可以绕过浏览器页面,用 curl、脚本或改包直接请求上传接口。accept 只是文件选择器提示,不是强制安全策略。
file.type 也来自浏览器识别和客户端信息,不能作为唯一依据。
四、服务端必须做什么
服务端要重新校验:
| 校验项 | 说明 |
|---|---|
| 大小限制 | 防磁盘和带宽打爆 |
| 文件头 | 判断真实类型 |
| 文件名 | 重命名,避免路径穿越 |
| 存储域 | 和主站隔离 |
| 扫描 | 病毒、恶意内容 |
| 权限 | 下载访问鉴权 |
上传目录不要允许执行脚本。
五、预览也有安全问题
前端预览图片时常用 Object URL:
const url = URL.createObjectURL(file)
img.src = url
用完要 URL.revokeObjectURL(url)。对于 SVG、HTML、PDF 等可执行或复杂格式,预览要非常谨慎,避免脚本执行或插件漏洞。
六、文件名展示要转义
文件名来自用户输入,展示时不能直接插入 HTML。
nameEl.textContent = file.name
不要用 innerHTML = file.name,否则特殊文件名可能造成 DOM 注入。
七、常见误区与追问
- 误区:input accept 能防止上传非法文件。 accept 只是选择器提示,接口必须服务端校验。
- 误区:file.type 一定可信。 MIME 可能为空或被伪造,服务端要看文件头和内容。
- 误区:图片上传就没有 XSS。 SVG、文件名、预览容器和解析器都可能带来风险。
- 追问:为什么上传文件最好独立域名存储? 降低和主站 Cookie、脚本执行上下文耦合的风险。
- 追问:前端如何防超大文件? 读取
file.size做体验拦截,但服务端仍要限制请求体。 - 追问:文件名如何处理? 前端展示转义,服务端重命名并禁止路径穿越。
八、加强记忆
上传安全记成“两层”:前端拦体验,后端保安全。类型、大小、文件名、预览、存储域和权限都要管,尤其别信客户端给你的任何文件信息。