必须校验登录态和文件类型:用authorization头或session验证身份,服务端通过mime类型白名单(如image/jpeg)和magic number校验文件,禁用前端后缀判断,限制上传大小及文件数。

上传前必须校验用户登录态和文件类型
没做身份校验就开放 POST /upload 接口,等于把服务器文件系统直接暴露给任何人。Gin 本身不带 session 或 auth 中间件,得自己用 gin.Context.Get() 或从 c.Request.Header.Get("Authorization") 提取 token,再查数据库或 JWT 解析验证。同时,仅靠前端 `` 完全不可信——攻击者改个请求头就能传 shell 脚本。必须在服务端用 c.Request.MultipartForm.File["file"][0].Header.Get("Content-Type") 拿真实 MIME 类型,并白名单比对(比如只允许 "image/jpeg"、"application/pdf")。
- 别依赖
filepath.Ext()判断后缀,攻击者可上传shell.php.jpg - MIME 类型也要防伪造:读取文件前几个字节做 magic number 校验(例如用
github.com/h2non/filetype) - 如果用 JWT,注意检查
exp和iss,别只 decode 不 verify
限制单次上传大小和总文件数
Gin 默认不限制 multipart 解析大小,恶意用户发个 10GB 文件会打爆内存或磁盘。必须在路由注册前调用 gin.SetMode(gin.ReleaseMode) 后,用 router.MaxMultipartMemory = 8 (8MB)设全局上限;再在 handler 里用 <code>c.Request.MultipartForm.Value["user_id"] 等字段做业务级限流——比如同一 user_id 10 分钟内最多传 5 个文件。
-
MaxMultipartMemory单位是字节,8 是 8MB,设太大易 OOM - 不要在 handler 里用
c.FormFile()后才校验大小,此时文件已进内存 - 用 Redis 记录
upload:uid:{user_id}的时间戳列表,超限直接c.AbortWithStatusJSON(400, gin.H{"error": "upload limit exceeded"})
保存路径必须隔离用户且禁用执行权限
用户上传的文件绝不能存到 Web 可访问的静态目录下,更不能让文件名直接拼进 os.OpenFile() 路径。正确做法是:生成随机文件名(如 uuid.New().String()),保存到独立目录(如 /data/uploads/),并确保该目录 Linux 权限为 755 且属主不是运行 Gin 的用户(避免被提权执行)。Nginx 反向代理时,用 alias 映射到该目录,并加 location ~ \.(php|sh|py)$ { deny all; } 防解析。
- 绝对禁止用
c.PostForm("filename")做保存名,否则 `../../../etc/passwd` 就能写入任意路径 - 保存前用
filepath.Clean()处理路径,但不如彻底不用原始名来得安全 - 如果需要原文件名,存在数据库字段里,别写进文件系统路径
上传后立即扫描病毒并异步处理
即使过了 MIME 和扩展名校验,也不能保证文件无害。Linux 下可用 clamav 的命令行工具,在 os.Rename() 到最终目录前调用 exec.Command("clamdscan", "--fdpass", tempFile.Name())。扫描耗时,务必用 goroutine 异步做,主流程只返回“已接收”,再通过 WebSocket 或消息队列通知前端结果。若扫描失败,立刻 os.Remove() 并记录日志。
- 别用
clamscan(单次启动开销大),优先配clamd守护进程 +clamdscan - 异步任务需加重试和死信机制,避免病毒文件堆积
- 扫描结果日志要脱敏,别把完整文件路径打到 stdout 里
multipart/form-data 解析之前挂载,否则 c.MustGet("user_id") 会 panic。











