Files
tamabox/internal/route/autoban.go
T
tamakyi 7d52155411 fix(honeypot): 蜜罐封禁与 enable_auto_ban 解耦 + 泄露扫描后缀匹配
- autoban: 新增 RecordHoneypot 独立通道(不受 enabled 约束)+ 独立阈值/时长;
  IsBanned 不再因 enable_auto_ban=false 而忽略已有封禁
- honeypot: 新增 honeypotSuffixes 后缀匹配,覆盖 /backup.zip、/db.sql、/id_rsa.pem
- 新增配置 honeypot_threshold / honeypot_ban_minutes
- 补测试:autoban/honeypot_test.go、route 侧禁用自动封禁仍封禁
2026-10-21 12:36:33 +08:00

50 lines
2.0 KiB
Go

package route
import (
"net/http"
"github.com/flamego/flamego"
"github.com/tamakyi/TamaBox/internal/security/autoban"
"github.com/tamakyi/TamaBox/internal/security/honeypot"
"github.com/tamakyi/TamaBox/internal/shared"
)
// autobanMiddleware 自动封禁中间件:挂载在静态黑名单之后、业务路由之前。
// - 已封禁 IP 直接 403,不再进入业务
// - 敏感路径命中 → 计入窗口统计(达到阈值触发临时封禁 + 邮件告警)
// - 真正的 404 探测由 f.NotFound 处理器单独计数(见 route.go),
// 不再把业务性的"302 → /"(语言切换 / 登出 / 投票不存在 / 黑名单跳转等)误判为扫描。
// - 核心检测逻辑见 internal/security/autoban(可独立测试);告警发送函数由本层注入。
func autobanMiddleware(b *autoban.Banner, hp *honeypot.Honeypot) flamego.Handler {
return func(ctx flamego.Context) {
// 经 shared.LimitKey 归一化后再计数与判定:IPv4 原样,IPv6 聚合到 /64。
// 否则攻击者可在单个 /64 内轮换地址,使每个键只命中一次而永不触发阈值。
// 记录与判定必须使用同一个键,否则封禁将无法命中。
ip := shared.LimitKey(shared.ResolveClientIP(ctx.Request().Request))
if ip == "" {
return
}
if b.IsBanned(ip) {
ctx.ResponseWriter().WriteHeader(http.StatusForbidden)
_, _ = ctx.ResponseWriter().Write([]byte("403 Forbidden"))
return
}
path := ctx.Request().URL.Path
kind := autoban.KindNone
// 蜜罐命中的路径由蜜罐处理器自己计数(它需要同时知道「是否触发了封禁」
// 以便落库)。若这里再记一次,同一次请求会被计两遍,阈值实际减半。
if hp == nil || !hp.Match(path) {
if autoban.IsSensitivePath(path) {
kind = autoban.KindSensitive
}
}
if kind != autoban.KindNone {
defer b.Record(ip, path, kind)
}
// 让下游业务路由先执行,敏感路径的计数延后到响应返回后记录。
ctx.Next()
}
}