fix(security): 修复 P1 高危项(OAuth2 state / 伪造客户端IP / CRLF 邮件头注入)

- OAuth2: state 改为 crypto/rand 随机值并写入独立短期 cookie
  (主会话为 SameSite=Strict,跨站回调不携带,不能放主会话);
  回调用 ConstantTimeCompare 校验 state,缺失/不匹配返回 403,
  校验后立即清除保证一次性使用。原硬编码 mailgo_oauth2_state
  可被利用做授权码注入/登录 CSRF。
- 代理信任: engine.SetTrustedProxies 仅信任 127.0.0.1/::1。
  外部直连时 X-Forwarded-For 完全不可信,防止伪造客户端 IP
  绕过登录封禁或恶意封禁他人;本机 Caddy/Nginx 转发不受影响。
- CRLF 注入: Web 写信的 To/Cc/Subject 及附件文件名不再原样拼入
  MIME 头。新增 sanitizeHeaderField(strip CR/LF/NUL)、
  subject 按 RFC 2047 编码、附件名用 mime.FormatMediaType
  (RFC 2231);附件下载的 Content-Disposition 同步修复。
  消息构建抽为 buildOutgoingMessage 纯函数便于测试。
- test: 新增 13 个回归测试(trustedproxy / mail_injection /
  oauth2_state),覆盖伪造 XFF、注入载荷、state 校验全部分支。
This commit is contained in:
2026-08-19 16:31:25 +08:00
parent 551ed981de
commit c725d0b91e
8 changed files with 559 additions and 82 deletions
+1 -1
View File
@@ -860,7 +860,7 @@ func (h *AdminHandler) AdminDownloadAttachment(c *gin.Context) {
return
}
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", att.FileName))
c.Header("Content-Disposition", formatContentDisposition(att.FileName))
c.Data(http.StatusOK, att.ContentType, data)
}