直接使用 file.filename 保存文件会导致路径遍历漏洞,因浏览器可能传入 ../、空字节等恶意路径;须手动安全重命名:剥离路径(filepath.base)、生成随机前缀(如 uuid)、白名单校验后缀(如 .png/.pdf),再用 c.saveuploadedfile() 写入安全路径。

为什么不能直接用 file.Filename 保存文件
因为浏览器传来的 file.Filename 可能含 ../、空字节、控制字符或任意路径片段,直接拼进 os.Create() 或 c.SaveUploadedFile() 会触发路径遍历(Path Traversal),导致文件写入任意目录,比如 ../../../etc/passwd。Gin 不做自动清洗,这一步必须手动防御。
安全重命名的三步操作
核心是剥离原始路径、生成不可预测名、保留可读后缀。推荐组合使用:
-
filepath.Base(file.Filename)截掉所有路径部分,只留原始文件名 - 用
uuid.New().String()或时间戳 + 随机数生成前缀(避免重复和猜测) - 用
path.Ext()提取合法后缀,并白名单校验(如只允许.png、.pdf)
示例代码片段:
ext := path.Ext(filepath.Base(header.Filename))
if !slices.Contains([]string{".jpg", ".jpeg", ".png", ".pdf"}, strings.ToLower(ext)) {
c.AbortWithStatusJSON(400, gin.H{"error": "unsupported file extension"})
return
}
newFilename := fmt.Sprintf("%s%s", uuid.New().String(), ext)
dstPath := filepath.Join(uploadDir, newFilename)
c.SaveUploadedFile 会覆盖重命名逻辑吗
会——而且它内部直接用你传入的 dst 路径调用 os.Create(),不做任何文件名清洗或校验。所以如果你传的是 "./uploads/" + file.Filename,就等于把风险全交给了客户端。
正确做法是:先自己构造安全的 dst,再传给 c.SaveUploadedFile();或者更可控地用 file.Open() + io.Copy() 流式写入,这样能精确控制每一步。
容易被忽略的细节
重命名不是加个 UUID 就完事。你还得确保:
- 目标目录存在且有写权限(
os.MkdirAll(uploadDir, 0755)) - 上传前已限制大小(
r.MaxMultipartMemory = 32 ),否则大文件可能在重命名前就 OOM - 如果用
file.Open(),必须defer src.Close(),否则句柄泄漏 - 并发上传时,UUID 足够唯一,但若用时间戳+计数器,需加锁或改用原子操作
真正卡住人的从来不是“怎么生成新名字”,而是没意识到:重命名只是安全链中一环,漏掉路径清洗、后缀校验、目录创建或资源释放中的任一环节,都可能让整个防护失效。











