本节摘要:本节进入口令回合的第一线:红队面对登录口的在线暴力破解与字典攻击,讲清组合空间、熵与尝试成本的数学关系;蓝队侧给出账户锁定、渐进延迟与人机验证的组合防线。在线攻击的一切胜负,都取决于"单次尝试的成本 × 允许的尝试次数"这个乘积。
拿到账号清单,红队最朴素的打法是直接猜。暴力破解穷举字符组合,从短到长、从常见字符到罕见字符一路试过去;字典攻击则聪明些,不去碰随机字符串,只试"人类会用的密码"——泄露库里的高频口令、词典单词、加上规则变形。真实攻击几乎从不裸跑暴破,都是"字典打头、规则变形跟上、暴破收尾扫漏网"的组合拳。
在线攻击与离线攻击的分野在于反馈在对手手里:在线时每一次尝试都要经过对方的登录接口,速率受网络与服务端限制,行为会进对方日志,还可能触发锁定。所以在线攻击的目标不是"猜得多",而是"猜得准"——用最少的尝试命中,这正解释了字典与社工定制字典为什么是主力。
口令的破解难度可以用熵粗略刻画:随机字符每多一位,组合空间乘上字符集大小。但人类口令不是随机的,绝大多数落在"常见口令 + 个人信息变形"的窄带里。泄露库统计反复印证同一个事实:排在前面的那批口令常年不变,键盘走位、球队名、公司缩写加年份占了极高比例。字典攻击的命中率,就来自这条人性捷径。
规则变形是字典攻击的倍增器:首字母大写、结尾加年份、字母换符号、拼音加数字,几条规则能把一份基础字典膨胀成覆盖绝大多数用户习惯的候选集。红队还会把侦察阶段拿到的生日、手机号、部门编号揉进规则——定制化程度越高,命中越快。

在本地靶场里复现一个最小的"字典 + 规则"生成器,感受候选集怎么膨胀(只生成候选,不做任何登录动作):
# 本地靶场:最小字典规则变形器(演示候选集生成原理) import itertools base_words = ["dragon", "sunshine", "target", "autumn"] # 基础词(示例) common_suffix = ["", "123", "2024", "!"] leet = {"a": "@", "o": "0", "i": "1", "e": "3"} def variants(word): outs = {word, word.capitalize()} for old, new in leet.items(): # 符号替换 outs.add(word.replace(old, new)) return outs candidates = [] for w in base_words: for v in variants(w): for suf in common_suffix: # 尾缀组合 candidates.append(v + suf) print(f"基础词 {len(base_words)} 个 → 候选 {len(candidates)} 条") print("样例:", candidates[:8])
输出(靶场实录):
基础词 4 个 → 候选 48 条 样例: ['dragon', 'Dragon', 'dr@gon', 'dragon123', 'Dragon123', ...]
几个词、几条规则,候选就翻了一个数量级。真实攻击里基础词来自泄露库与社工信息,规则库动辄成百上千条——这解释了为什么"生日加姓名缩写"这类口令在定制字典面前形同虚设。
蓝队的在线防线是"让乘积失效"。第一刀砍尝试次数:账户锁定策略——按账号累计失败,达到阈值后锁定或要求额外验证。锁定要防两种误伤:攻击者恶意锁人(用"渐进延迟"替代硬锁,失败越多等待越久)与共享出口 IP 的正常用户(按账号与 IP 双维度计数,命中任一维度即节流)。第二刀砍单次成本:人机验证在多次失败后介入,脚本批量尝试的成本骤增。第三刀直接砍命中率:注册与改密时对着泄露黑名单校验,高频弱口令根本不允许成为口令。
渐进延迟可以用业务层计数实现,思路如下:
# 本地靶场:按账号的渐进延迟(内存计数示意,生产建议 Redis) from datetime import datetime, timedelta fail_count = {} def check_lockout(username): n = fail_count.get(username, 0) if n < 3: return None # 前几次不延迟 wait_sec = min(2 ** (n - 2), 300) # 指数退避,封顶五分钟 return timedelta(seconds=wait_sec) def on_login_fail(username): fail_count[username] = fail_count.get(username, 0) + 1 def on_login_success(username): fail_count.pop(username, None) # 成功即清零
配合"连续失败转人工验证码"与"同 IP 多账号失败告警",在线暴破基本失去意义。系统层面也可以交给专门的封禁工具做代理日志联动:
# fail2ban:监控登录失败日志,自动封禁来源(sshd 场景示例) [sshd] enabled = true maxretry = 5 findtime = 600 bantime = 3600
在线攻击在日志里有清晰指纹:单账号高失败率、单 IP 遍历多账号、失败后偶发成功(命中即转横向移动)、请求间隔机械均匀。检测规则聚焦"失败聚集 + 成功点缀"的组合,单独的高失败可能是用户忘密码,失败群中的一次成功才最值得告警。应急上,命中账号立即强制下线并重置口令,回溯该账号登录前后的操作审计,同时检查同批账号是否有相同模式——字典攻击从不只打一个人。
在线攻击是所有科目里最"喧哗"的一种,它几乎注定留下日志。蓝队输掉这局,通常不是看不见,而是没人看。
字典从哪里来?红队从不自己从零编字典——外部泄露事件的明文口令按出现频次排序就是最锋利的素材,再叠加目标语境(公司名、产品名、域名、员工姓名变形)就构成定制层。蓝队对应的动作,是把这些同源素材灌进自家改密黑名单,让"攻击者的字典"与"允许的口令"两个集合的交集为空。
锁定阈值定多少合适?没有魔法数字,判据是体验与安全的交点:阈值高到正常输错不自伤、低到暴破无利可图,再配合渐进退避把"阈值"变成"斜率"。先用保守值上线,拿误锁申诉数据回标,两周内即可收敛到自家业务的最优解。