Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
6.2 KiB
autoban 自动封禁误伤诊断报告
日期:2026-09-05 · 分析对象:internal/security/autoban + internal/route/autoban.go + internal/shared/ip.go
一、当前配置与生效机制
| 配置 | 你的值 | sample 默认 | 语义 |
|---|---|---|---|
| trust_proxy | true | true | 信任 CDN 权威头解析客户端 IP |
| enable_auto_ban | true | true | 启用自动封禁 |
| ban_threshold_sensitive | 10 | 10 | 窗口内敏感路径命中 ≥10 触发封禁 |
| ban_threshold_404 | 20 | 20 | 窗口内 404 ≥20 触发封禁 |
| ban_window_seconds | 10 | 60 | 统计窗口(滚动) |
| ban_duration_minutes | 15 | 15 | 封禁时长 |
判定逻辑(autoban.go Record / IsBanned):
- 每个请求按「归一化客户端 IP」计数(IPv4 原样;IPv6 聚合到 /64 →
LimitKey) - 两类事件独立计数,
敏感≥10 OR 404≥20(10s 滚动窗口内)任一达标 → 封禁该键 15 分钟 - 已封禁键上所有后续请求直接 403
两个计数入口(全站仅此两处):
- 中间件
autoban.go:39:请求路径命中敏感词表(/admin、/.env、/wp-admin、/config、/db…共 39 条前缀)→ defer 计数 f.NotFound(route.go:403):一切未匹配路由 → 404 计数后 Redirect("/")
二、排除项(已静态验证,非误伤来源)
- ✅ 业务路由零碰撞:全站注册路由 + 模板 77 个站内 URL 与 39 条敏感前缀无一匹配——正常页面打开不会误计敏感
- ✅ favicon.ico / robots.txt / sitemap / llms 均有 embed handler,不产生 404
- ✅ 404 处理器与业务性 302(语言切换/登出/投票不存在)已分离,不误计
三、误伤根因(按可能性排序)
🔴 P0-A:客户端 IP「键折叠」→ 多用户共享同一计数键(最可能)
正常用户自己 10s 内绝不可能凑出 10 次敏感或 20 次 404(每秒 2 个 404 的强度),所以能误伤正常用户,唯一机制是"别人的探测被记到了你的键上"。trust_proxy=true 的解析链(ip.go ResolveClientIP):
- 先看
CF-Connecting-IP/EO-Client-IP(权威头) - 都没有 → 回退
RemoteAddr
折叠场景:
- 若 EO 回源未开启/未注入
EO-Client-IP(需在 EO 控制台确认回源透传配置),国内请求全部回退到 EO 边缘 IP(43.174.247.x 新加坡) → 全体大陆用户共享一个键。扫描器也走同一边缘 → 任一扫描触发 → 全国用户 403 15 分钟 - 海外 CF tunnel:cloudflared 必注
CF-Connecting-IP,正常不折叠;但若异常缺失则回退127.0.0.1→ 所有海外用户共享同一键 - sample 注释原话即为该坑:"否则解析到的将是 Cloudflare 边缘 IP,自动封禁会误伤所有访客"
自检方法:配置 security_alert_email,看封禁告警里的 IP —— 若出现 43.174.247.43 / 43.174.246.43 / 127.0.0.1 这类非用户 IP,即实锤折叠。
🔴 P0-B:运营商 CGNAT / 共享出口连坐(国内常态)
国内移动/联通/电信家宽普遍 NAT444,成百上千真实用户共用一个公网 IPv4。该池内任一被黑设备/扫描器探测你的站点(攻击者扫全网,你的 EO 域名是公网目标)→ 触发阈值 → 整个出口池 403 15 分钟。你本人被误伤大概率由此而来(你的出口 IP 里别人在扫站)。
🟠 P1-C:IPv6 /64 聚合连坐
LimitKey 将 IPv6 聚合到 /64。若运营商分配粒度粗(多户共 /64)或会话前缀抖动,同段邻居的探测会连坐你。相对少见,但配置了 IPv6 解析时存在。
🟠 P1-D:双 CDN 权威头互信被伪造(定向误伤)
若未按 outputs/edgeone-cloudflare-dual-cdn-guide.md 在两家 CDN 规则引擎删除"对方 CDN"的权威头,攻击者可伪造 CF-Connecting-IP: <受害者IP> 直连源站/借道 EO → 探测全部记到受害者头上 → 把任意人"定向打成 403"。属攻击向量而非随机误伤,但同样表现为"我没干什么就 403"。
四、修复建议(分级)
P0-① 先止血(纯配置,立即可做,无需改代码)
ban_window_seconds = 60 ; 10s 窗口对正常浏览太苛刻,回到默认
ban_threshold_sensitive = 30 ; 敏感命中门槛抬高(真扫描 1s 内远超此值)
ban_threshold_404 = 100 ; 404 门槛抬高(正常用户+偶发爬虫不会到)
security_alert_email = <你的邮箱> ; 关键:先打开告警看封禁 IP,定位是否"键折叠"
窗口越短反而越难误伤,你已比默认更保守仍误伤 → 证明误伤不是参数敏感,而是键共享,调参只能缓解不能根治。
P0-② 确认并修复 IP 解析(治本,需查服务器/控制台)
- 源站侧:抓一条真实请求,确认带没带
EO-Client-IP/CF-Connecting-IP头(nginx access log 或临时 echo 头调试)- EO 域名确认开启「回源客户端 IP 透传」;无则开
- 源站锁定:防火墙仅放行两家 CDN 回源网段(sample 已警告;未锁定时任何人可直连伪造头)
- 双 CDN 按指南删对端权威头
P1-① 代码增强(需你批准后实施)
| 项 | 改动 | 效果 |
|---|---|---|
| 登录用户豁免 | 有有效 session 的请求不计数/不封禁 | 管理员/正常登录用户永不误伤(扫描器无 cookie) |
| 封禁日志 | 封禁时 logrus 打印 ip+路径样本+UA | 事后可归因,不再"黑盒" |
| UA 特征二次确认 | 敏感命中要求 UA 缺浏览器特征才加速计数 | 降低误伤 |
P2 观察项
- 若确认 P0-A 折叠实锤:可考虑对"CDN 边缘类"键整体排除(仅当 RemoteAddr 属于 CDN 且权威头缺失时才回退 RemoteAddr——需小改 ip.go)
- 监控告警邮件中的封禁频率与 IP 类型分布
五、证据清单(本次分析依据)
- internal/security/autoban/autoban.go:阈值判定、窗口、清理
- internal/route/autoban.go:中间件挂载、403 直接返回
- internal/route/route.go:78-84、146、403:实例化、全局挂载、404 计数
- internal/shared/ip.go:ResolveClientIP 信任链 + LimitKey /64 聚合 + 双 CDN 警告注释
- conf/app.sample.ini:默认值与设计约束说明
- 静态碰撞分析:业务路由/模板 URL × 敏感词表 = 零命中