fix(security): 修复 P2 中危项(cookie/协议限速/路径遍历/默认口令/中继TLS/安全头/信息泄露)

- 会话 cookie 增加 Secure 标志;新增 [web].cookie_secure 配置
  (默认 true,仅本地 HTTP 调试关闭;缺失字段按安全默认处理)
- SMTP/IMAP/POP3 认证接入封禁体系(store.RecordAuthFailure 与 Web
  共用 ban_entries):失败计数达 ban.max_fail_attempts 即封禁 IP,
  已封禁 IP 拒绝认证,堵住协议层暴力破解
- 附件存储路径遍历防护重写:FullPath 白名单校验(UUID 文件名格式)
  + baseDir 前缀兜底,非法路径返回错误;Save 扩展名白名单化
- 初始管理员不再使用 admin/admin:密码取 MAILGO_ADMIN_PASSWORD 或
  随机生成并打印一次;新增 MustChangePassword 首登强制改密
  (管理员重置密码同样触发)
- 外发中继默认验证 TLS 证书(保护 AUTH 凭据,防 MITM),直投 MX
  保持机会式 TLS;新增 outbound.relay_tls_insecure 开关(默认 false)
- 新增安全响应头中间件:HSTS、X-Frame-Options DENY、nosniff、
  Referrer-Policy、基础 CSP(frame-ancestors 'none' 防点击劫持,
  connect-src/form-action 'self' 防数据外泄)
- LDAP/OAuth 登录错误统一为通用文案,原始错误只写日志,
  不再回显邮箱/内部细节(防用户枚举与信息泄露)
- 新增 25 个回归测试:cookie 标志、封禁阈值、路径遍历用例、
  中继 TLS 验证(自签证书 STARTTLS 集成)、安全头、OAuth 文案

部署注意:升级后所有会话失效需重新登录;若直接以 HTTP 提供
服务需显式配置 cookie_secure = false。
This commit is contained in:
2026-08-19 16:45:21 +08:00
parent c725d0b91e
commit 3f28ec20f4
26 changed files with 799 additions and 101 deletions
+11
View File
@@ -10,6 +10,7 @@ import (
"strings"
"time"
"mail_go/config"
"mail_go/internal/db"
"mail_go/internal/mailutil"
"mail_go/internal/store"
@@ -26,12 +27,22 @@ import (
// imapBackend implements backend.Backend.
type imapBackend struct {
stores *store.Stores
banCfg config.BanConfig
}
// Login authenticates a user by email and password.
func (b *imapBackend) Login(connInfo *imap.ConnInfo, username, password string) (backend.User, error) {
clientIP := store.ClientIPFromAddr(connInfo.RemoteAddr)
// 已封禁 IP 一律拒绝认证(防协议层暴力破解)
if banned, _ := b.stores.Bans.IsBanned(clientIP); banned {
return nil, backend.ErrInvalidCredentials
}
user, err := b.stores.Users.Authenticate(username, password)
if err != nil {
// 认证失败计数,达到阈值封禁(与 Web 登录共用 ban_entries
b.stores.RecordAuthFailure(clientIP, b.banCfg.MaxFailAttempts, b.banCfg.BanDurationMin)
return nil, fmt.Errorf("invalid credentials: %w", err)
}
+4 -2
View File
@@ -17,15 +17,17 @@ import (
type IMAPServer struct {
stores *store.Stores
cfg config.IMAPConfig
banCfg config.BanConfig
tlsLoader *tlsutil.Loader
}
// NewIMAPServer creates a new IMAP server instance. tlsLoader may be nil
// when TLS is not configured.
func NewIMAPServer(cfg config.IMAPConfig, stores *store.Stores, tlsLoader *tlsutil.Loader) *IMAPServer {
func NewIMAPServer(cfg config.IMAPConfig, stores *store.Stores, tlsLoader *tlsutil.Loader, banCfg config.BanConfig) *IMAPServer {
return &IMAPServer{
stores: stores,
cfg: cfg,
banCfg: banCfg,
tlsLoader: tlsLoader,
}
}
@@ -40,7 +42,7 @@ func (s *IMAPServer) tlsConfig() (*tls.Config, error) {
// newServer creates a configured imapserver.Server with the given address.
func (s *IMAPServer) newServer(addr string, tlsConfig *tls.Config) *imapserver.Server {
be := &imapBackend{stores: s.stores}
be := &imapBackend{stores: s.stores, banCfg: s.banCfg}
srv := imapserver.New(be)
srv.Addr = addr
srv.TLSConfig = tlsConfig