fix: 文章图片上传增加内容解码验证,防伪扩展名和图片投毒

- 所有格式(JPEG/PNG/GIF/WebP)上传后强制解码验证是否为有效图片
- 解码失败直接拒绝,不再回退原样存储
- 消除 .php 改名为 .jpg 绕过检查的安全漏洞
- JPEG/PNG 解码与 WebP 编码合并为一次解码,避免重复读
This commit is contained in:
2026-05-31 03:04:45 +08:00
parent 5fbd010b71
commit 6ea4e5365e

View File

@ -411,13 +411,21 @@ func (ctrl *PostController) UploadImage(c *gin.Context) {
ts := time.Now().UnixMilli() ts := time.Now().UnixMilli()
var savePath, url string var savePath, url string
// JPEG/PNG → WebP 压缩存储保留原尺寸quality 80 isJPEGPNG := ext == ".jpg" || ext == ".jpeg" || ext == ".png"
if ext == ".jpg" || ext == ".jpeg" || ext == ".png" { isWebP := ext == ".webp"
isGIF := ext == ".gif"
// 强制解码验证文件是否为有效图片(防伪扩展名、图片投毒),验证失败直接拒绝
// JPEG/PNG解码后顺便做 WebP 压缩
if isJPEGPNG {
img, _, err := image.Decode(bytes.NewReader(data)) img, _, err := image.Decode(bytes.NewReader(data))
if err == nil { if err != nil {
common.Error(c, http.StatusBadRequest, "图片格式无效,请上传有效的图片文件")
return
}
// WebP 压缩存储保留原尺寸quality 80
var buf bytes.Buffer var buf bytes.Buffer
if err := webp.Encode(&buf, img, &webp.Options{Quality: 80}); err == nil { if err := webp.Encode(&buf, img, &webp.Options{Quality: 80}); err == nil {
// 仅当 WebP 比原文件小时才采用,否则回退原格式
if buf.Len() > 0 && buf.Len() < len(data) { if buf.Len() > 0 && buf.Len() < len(data) {
filename := fmt.Sprintf("%d_%d.webp", uid, ts) filename := fmt.Sprintf("%d_%d.webp", uid, ts)
savePath = filepath.Join(storageDir, filename) savePath = filepath.Join(storageDir, filename)
@ -427,9 +435,24 @@ func (ctrl *PostController) UploadImage(c *gin.Context) {
} }
} }
} }
// GIF仅解码验证不转换
if isGIF {
if _, _, err := image.Decode(bytes.NewReader(data)); err != nil {
common.Error(c, http.StatusBadRequest, "图片格式无效,请上传有效的图片文件")
return
}
} }
// 回退GIF / WebP / 转换失败 / 转换后反而更大 → 原样存储 // WebP仅解码验证不重复编码
if isWebP {
if _, err := webp.Decode(bytes.NewReader(data)); err != nil {
common.Error(c, http.StatusBadRequest, "图片格式无效,请上传有效的图片文件")
return
}
}
// 原格式存储WebP / GIF / 转换后反而更大)
if savePath == "" { if savePath == "" {
filename := fmt.Sprintf("%d_%d%s", uid, ts, ext) filename := fmt.Sprintf("%d_%d%s", uid, ts, ext)
savePath = filepath.Join(storageDir, filename) savePath = filepath.Join(storageDir, filename)