文件格式校验必须读取文件头魔数并比对真实字节模式,使用 io.readfull 安全读取前 n 字节(推荐 8 字节),jpeg、png、pdf 分别校验对应 magic bytes,读完后需 file.seek(0, 0) 重置指针,遇 io.errunexpectedeof 应拒绝。

文件格式校验不能只看 Filename 或 Content-Type 头,必须读取文件头(magic bytes)并比对真实字节模式,否则攻击者可上传 shell.php.jpg 绕过检查。
用 io.ReadFull 安全读取前 N 字节判断格式
浏览器上传的 multipart.FileHeader 只提供声明大小和不可信 MIME 类型,真正可靠的格式识别依赖前几个字节。直接 file.Read(buf) 可能只读到部分数据导致误判,必须用 io.ReadFull 保证读满指定长度。
- JPEG 必须以
[]byte{0xFF, 0xD8}开头,PNG 是[]byte{0x89, 0x50, 0x4E, 0x47},PDF 是[]byte{0x25, 0x50, 0x44, 0x46} - 缓冲区大小至少为最长魔数长度(如 PDF 的 4 字节),推荐统一用 8 字节避免边界问题
- 读完后调用
file.Seek(0, 0)重置指针,否则后续保存会从中间开始写入 - 若
io.ReadFull返回io.ErrUnexpectedEOF,说明文件太短,应拒绝(如空文件或截断文件)
别用 http.DetectContentType 替代魔数校验
http.DetectContentType 内部也是读前 512 字节做 magic 匹配,但它返回的是宽松 MIME 类型(如把某些二进制识别为 application/octet-stream),且对极小文件(text/plain,无法满足精确控制需求。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 它不校验文件是否“符合该格式规范”,只做粗略分类;例如一个 JPEG 文件头部被篡改但仍能被浏览器渲染,
DetectContentType可能仍返回image/jpeg - 业务要求严格时(如仅允许标准 PNG),必须手动比对魔数,而不是依赖其返回值做 switch
- 若坚持使用,需额外检查
header.Size 场景:此时 <code>DetectContentType输入不足,结果不可信
校验顺序必须是「大小 → 魔数 → 扩展名」
顺序错乱会导致资源浪费或逻辑漏洞。比如先检查扩展名再读魔数,攻击者传 photo.png 但内容是 ZIP,服务端已分配内存准备处理 PNG 却在魔数阶段失败。
- 第一步用
header.Size快速拦截超大文件(注意这只是客户端声明值,仅作初步过滤) - 第二步用
io.ReadFull读魔数,失败立即返回 HTTP 400,不继续解析 - 第三步才检查扩展名是否与魔数逻辑一致(如魔数是 JPEG,扩展名只能是
.jpg或.jpeg),不一致则拒绝——防止evil.jpg.php这类双扩展名绕过 - 所有校验必须在
file被写入磁盘前完成,任何一步失败都要defer file.Close()并中断流程
魔数校验本身不防内容篡改,它只确认“文件开头符合某格式规范”。如果业务需要强完整性保障(如用户上传合同 PDF 后不允许被修改),必须叠加 SHA256 校验,且哈希计算要在魔数校验通过后、文件流重置前进行——避免重复读取。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










