sun
|
6ed25e8e4b
|
fix(ratelimit): 强制清理按最旧活跃时间排序,避免随机删活跃用户
原版用 'for k := range rl.requests' 直接删除超限条目,Go map 遍历顺序
随机,可能命中 timestamp 仍在窗口内的活跃用户,导致其下次请求拿到新配额
(限流语义被破坏)。
同时存在 abuse vector: 攻击者用大量伪造 IP 撑爆 map 触发清理,反而
'清洗'掉自己留的活跃条目,绕过限流。
修复:
- 收集 (key, lastTs) 到 slice,按 lastTs 升序排(最旧在前)
- lastTs 取 timestamps 末尾(Allow 总 append 到末尾,即最近请求时间)
- 从前往后逐个 delete,直到 len(rl.requests) <= MaxRateLimiterEntries/2
- 阈值/触发条件/warning 日志保持不变
复杂度 O(n log n),仅在超 MaxRateLimiterEntries(10w)时触发,代价可接受。
cleanup 仍在 Allow 入口的 rl.mutex.Lock() 区域内,无需额外锁。
|
2026-09-21 11:14:48 +08:00 |
|
sun
|
272565f736
|
fix: VUL-003 完整修复,启发式/精确双模式 XFF 解析
VUL-003 (中): X-Forwarded-For 信任链可被伪造 IP 绕过限流
本服务定位为公网入口,即使单人使用,公网暴露意味着攻击面
与公开服务等同,不能"够用就行"。
采用渐进式披露设计,平衡易用性与功能性:
1. 启发式模式(默认, 不设环境变量 或 TRUSTED_PROXY_HOPS=0)
- 从 XFF 链尾扫描,跳过私有 IP,返回第一个公网 IP
- 适合 90% 部署(单跳/多跳/直出),无需了解精确跳数
- 限制:多跳 CDN 场景下,限流粒度为"按 CDN 边缘 IP"
- 直出部署:整个 XFF 分支不会执行
2. 精确模式(TRUSTED_PROXY_HOPS=N, N>0)
- 从 XFF 链尾倒数第 N+1 个位置取值
- 精准到真实 client,需按实际反代跳数正确配置
- N=1:单跳反代;N=2:CDN+反代;以此类推
3. 两种模式都从链尾扫描
- XFF 首值是客户端可控的,信任首值等于信任攻击者
- 链尾由受控的反代添加,天然免疫伪造绕过
4. 默认值从 1 改为 0(行为变化)
- 旧默认:精确模式 N=1,取 XFF 末值
- 新默认:启发式模式,跳过链尾私有 IP
- 对单跳场景行为相同
- 对多跳/链尾含私有 IP 场景新版更准确(返回真实公网 IP)
5. 配套
- middleware/ratelimit_test.go:24 个表驱动测试用例,
覆盖直出/单跳/多跳/伪造/畸形/精确 N 边界,全部通过
- setting/config.go:LogStartupSummary 显示当前 XFF 模式
- .env.example:重写说明,标注默认行为 + 何时需配
- README.md:新增"反代拓扑与 X-Forwarded-For 解析"章节
(何时需要/两种模式/行为对比/为什么从链尾/启动日志验证)
6. 已知边界:TRUSTED_PROXY_HOPS=00 等被 Atoi 解析为 0 的
输入归入启发式模式,日志不会出现"精确模式 0 跳"矛盾输出。
|
2026-08-26 00:36:22 +08:00 |
|