本节摘要:本节是口令回合的蓝队专题:把前几节分散提到的锁定策略、速率限制与人机验证整合成一套可运营的防线。重点在参数设计与误伤治理——锁得住攻击者、放得过真实用户,才算合格;顺带交代验证码的选型与无障碍权衡。
口令回合前三局,蓝队一直在"见招拆招":暴破来了上锁定,喷洒来了做聚合检测。这一局换个打法,把三层机制一次性部署到位,让绝大多数自动化尝试在触达业务逻辑之前就被挡掉。三层各有分工:速率限制压请求频率,管住"打多快";账户锁定与渐进退避管住"对同一账号试几次";人机验证在行为可疑时插入一道交互成本,管住"到底是不是人"。
三层的位置也有讲究:网关层限速离攻击最近、拦截成本最低,但粒度粗(只见 IP 不见账号);业务层锁定粒度细(按账号计数),但要扛住恶意锁人的滥用;验证码夹在两者之间,作为"可疑但不确凿"时的升级手段。布防顺序遵循"先粗后细、先廉后贵"。
锁定策略的关键参数是阈值、窗口与处置方式。硬锁定(达到阈值直接封账号)防暴破最彻底,却给了攻击者"恶意锁人"的武器——对着高管账号故意输错,业务就瘫痪了。所以现代设计更倾向渐进退避:失败次数越多,下次尝试前的强制等待越长,指数增长封顶。攻击者的单账号成本随失败次数指数上升,正常用户多等几秒就能自愈,无人能被"锁死"。
人机验证的选型是体验与安全的跷跷板。图形点选类对脚本有阻遏,但对视障用户不友好且可被打码平台绕过;无感验证(行为指纹、设备指纹评分)体验最好,但强依赖风控模型成熟度;硬件挑战类(如加密 attest 的不可见验证)兼顾体验与强度,是近年的主流方向。工程上常见组合是"默认无感、低分出挑战、高危强验证"。

业务层的滑动窗口计数用 Redis 落地最顺手,关键在于窗口滑动而非固定分桶,避免攻击者卡整点边界:
# 业务层:按账号的滑动窗口失败计数 + 指数退避(Redis 版思路) import redis, time r = redis.Redis() WINDOW, THRESHOLD = 3600, 8 # 一小时窗口,阈值八次 def too_fast(username: str) -> float: """返回需等待的秒数;0 表示放行。""" key = f"login:fail:{username}" now = time.time() pipe = r.pipeline() pipe.zremrangebyscore(key, 0, now - WINDOW) # 清理窗口外记录 pipe.zcard(key) # 当前窗口失败数 _, fails = pipe.execute() if fails < 3: return 0 wait = min(2 ** (fails - 2), 300) # 指数退避,封顶五分钟 return wait def record_fail(username: str): key = f"login:fail:{username}" now = time.time() r.zadd(key, {str(now): now}) r.expire(key, WINDOW) def record_success(username: str): r.delete(f"login:fail:{username}") # 成功清零,用户自愈
验证码在多次失败后触发,触发逻辑与上面共用计数。网关层再叠一道按 IP 的粗限速,两层计数维度不同,攻击者绕过任何一层都会撞上另一层。
防线的可用性由误伤率决定。几种高频误伤场景要预先处置:共享出口(公司、校园网)多用户同 IP,按 IP 硬限速会伤及无辜,改成"IP 维度只告警不拦截、账号维度才拦截";跨国企业用户常态异地登录,"异地即风险"规则误报高,用设备指纹与历史行为加权评分替代二元判定;老年用户反复输错,退避封顶时长要设上限并保留客服通道。上线前拿真实流量回放验证误伤率,上线后持续跟踪申诉数据,阈值不是定一次就完事的常量。
⚠️ 常见坑:把锁定计数存在应用本地内存里且多实例不共享——攻击者的请求被负载均衡分到不同实例,每个实例都只看到一小部分失败,阈值形同虚设。计数必须进共享存储。
这套防线自身也需要被观测。运营看几组数:退避与锁定触发量的周环比(异常抬升说明有活动在进行);验证码挑战的通过率(骤降说明攻击者升级了自动化);误锁申诉率(超标说明阈值过紧);被拦截流量的账号分布(集中在少数账号是暴破,散布是喷洒)。把"防线拦下了什么"做成周报,是向管理层证明投入价值的最直接方式——口令回合打到这一局,蓝队终于在沙盘上把红队的进场路线整个封住了。
一条经验法则收尾:阈值定松了形同虚设,定紧了误伤真人。让数据说话——先用保守值上线,按误锁申诉与攻击拦截两类指标双向微调,两周内就能找到自家业务的最优解。
这套防线不是配完就算交付,上线前过一遍检查单,上线后拿压测说话。检查单六问:计数是否进了共享存储(多实例可见)?是否同时覆盖账号与 IP 双维度?等待时长是否只在持续失败时累计、成功即清零?验证码是否只在可疑时出现而非常态骚扰?人机验证是否有无障碍替代路径?封禁动作是否有申诉出口?六问全过才允许进生产。
压测口径同样要定好——安全机制自己不能成为拒绝服务放大器:
# 压测验收基线(示例) # 场景一:正常用户连续输错后的重试 # 期望:退避生效,第 N 次等待不超过封顶时长,成功后计数清零恢复如初 # 场景二:攻击者对单账号高频失败 # 期望:尝试吞吐被指数压低,日志保留完整,无级联故障 # 场景三:攻击者换 IP 对同账号轮试 # 期望:账号维度计数持续累计,不受 IP 轮换重置 # 场景四:攻击者打挂计数存储 # 期望:防线降级为网关层限速,登录功能不整体不可用
第四个场景最容易被忽略,也最致命——攻击者发现你的锁定计数依赖某个组件后,转而把该组件打挂,让所有用户的登录一起出问题。降级策略(组件不可用时回落到网关粗粒度限速)要在设计期就想好,别等事故现场即兴创作。