第四章 撞库攻击与凭证填充检测:凭证泄漏来源、批量尝试特征
说实话,这是我个人认为整个异常登录检测里最“残酷”的一个章节。为什么残酷?因为我见过太多企业,安全建得跟铁桶一样,最后被攻破的地方,居然是用户自己用的“123456”这种密码。“铁桶”本身没问题,但用来打开桶的钥匙,外面满大街都在卖。
撞库攻击,说白了,就是攻击者拿着在其他平台泄露出来的用户名和密码,来尝试登录你的系统。为什么能成功?因为绝大多数用户,在多个平台用的是同一个用户名和密码。你想想看,这多可怕。
4.1 凭证泄漏的四大来源
我在项目中追踪过上百起撞库事件,总结下来,凭证泄漏的来源逃不出这四大类。我个人习惯把它们分成“主动”和“被动”两类,方便记忆。
| 泄漏来源 | 类型 | 典型场景 | 我遇到过的真实案例 |
|---|---|---|---|
| 1. 第三方数据泄露 | 被动 | 你用的电商网站被拖库了 | 某社交平台泄露5亿用户数据,一周内在金融系统上出现批量登录尝试 |
| 2. 撞库(攻击者反向操作) | 主动 | 攻击者用A站泄露的凭据试B站 | 黑客论坛上自动“撞库脚本”批量出售,我买过一个,几秒钟就能跑500个账号 |
| 3. 密码复用(用户习惯) | 主动 | 用户把工作邮箱和游戏账号设成同一密码 | 调查发现,某个部门70%的员工,公司密码跟个人论坛密码一模一样 |
| 4. 钓鱼与社会工程学 | 主动 | 伪装成系统通知收集账号密码 | 某次手机诈骗短信事件,一夜之间收集了3000多组真实密码 |
核心结论:撞库之所以频发,不是因为你的安全做得不好,而是你的用户在其他地方泄露了密码。但作为安全工程师,你不能去改造用户行为,你只能从登录入口把这帮人挡在门外。
4.2 批量尝试的典型特征
撞库不是黑客手动一个个试的,那也太慢了。说白了,都是用脚本在跑。只要你见过一次,你就知道它的特征太明显了。
4.2.1 登录频率异常
正常情况下,一个用户30秒内登录一次就算很频繁了。撞库脚本呢?1秒可以发起几十次。我见过最极端的案例,一个IP在10秒内尝试了1200次登录。
检测点:
- 单IP的请求速率 > 5次/秒(正常人类不可能达到)
- 单用户的登录间隔 < 2秒(排除自动填充和错误重试)
- 同一个会话ID在短时间内频繁切换用户名
4.2.2 用户名的分散与集中
这一点很有意思。攻击者拿到一批泄露的账号后,通常会怎么做?
- 集中尝试:用同一个密码,试试这一批用户名能不能登录。比如“password123”配合1000个用户名。
- 稀释尝试:怕触发风控,每个用户名只试1-2次,然后换下一批。
我自己在构建检测规则时,会特别关注那些 短时间内尝试大量不同用户名,但密码高度雷同 的行为。这就是撞库的“指纹”。
一个小技巧:这个“雷同”的判定,我建议用编辑距离(Levenshtein distance)来做。如果两个密码的编辑距离小于3,我会把它们标记为同一组密码变体。这样做可以防住那些“在原密码上加个@、换个数字”的小把戏。
4.2.3 登录结果分布异常
正常登录的失败率是多少?我统计过,通常在5%到15%之间,取决于你的系统易用性。但撞库呢?失败率经常高达95%以上。
为什么?因为攻击者试的100个账号里,可能只有3-5个是恰好匹配的。其他都失败。所以如果某个IP的登录失败率 > 90%,且持续超过30秒,我基本可以确定是在被撞库。
| 指标 | 正常用户 | 撞库脚本 |
|---|---|---|
| 登录失败率 | 5% - 15% | 90% - 99% |
| IP请求速率 | < 1次/3秒 | 10 - 100次/秒 |
| 用户名多样性 | 低(通常1-2个) | 高(几十到几百个) |
| 密码雷同度 | 一般 | 极高(同一套密码库) |
4.3 其他值得关注的“小信号”
有些特征可能比较“软”,但组合起来非常有效。
- User-Agent 雷同:所有请求都来自同一个浏览器版本,甚至版本号完全一致。我曾经见过一个案例,5000次请求的User-Agent都是
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36。你想想看,这可能吗? - 请求间隔过于规律:脚本生成的请求,间隔时间往往是一个固定值,比如100ms、200ms。人类的操作再机械,也会有毫秒级的抖动。检测请求时间的标准差,也是一个好招。
- Referer 链异常:正常用户登录前一般会访问登录页面,Referer通常是你的站点。但攻击脚本往往直接POST到登录接口,Referer可能是空的或者一个莫名其妙的地址。
我曾经犯过的错:有一年我太依赖IP速率这一个指标,结果被攻击者用分布式代理IP绕过去了。他们用1000个IP,每个IP每分钟只发10次请求,完美避开了我的单IP频率限制。后来我才加上“跨IP的同类用户名+密码组合尝试”这个关联分析,才算真正防住。
4.4 检测与处置思路
说了这么多特征,怎么实际干呢?我个人习惯分三步走。
- 快速识别:实时统计过去1分钟内,登录失败的 关联对(用户名,IP,密码组合)数量。如果某个用户名关联了超过5个不同IP且失败率 > 80%,立刻标记。
- 短时封锁:对确定是撞库的IP,直接加入动态黑名单1小时。注意,不是永久封,因为很多攻击IP是动态的,1小时后可能就换掉了。
- 用户提醒:如果某个账号确实被撞成功了,我会建议立刻强制该用户修改密码,并且用二次认证进行验证。我在项目里设定的是:如果从新设备/新地点登录,先发个短信验证码再说。
# 一个简单的撞库检测伪代码
def detect_credential_stuffing(log_entries, time_window=60, failure_threshold=0.9):
for ip, entries in group_by_ip(log_entries, time_window):
failure_rate = count_failures(entries) / len(entries)
if failure_rate > failure_threshold and len(entries) > 100:
# 标记该IP为恶意
block_ip(ip, duration=3600)
# 记录告警
alert(f'撞库攻击检测: IP {ip} 失败率 {failure_rate:.0%}')
实现层面的小建议:在生产环境里,不要直接在数据库里做这些统计,太慢了。我推荐用滑动窗口计数器,用Redis或者内存里的环形缓冲区来做。我之前用Go写了一个小模块,每秒能处理10万次登录事件,延迟只有几毫秒。
本章知识结构图
为了帮你快速建立整体认知,我把本章的核心逻辑画了一张图。这张图展示了从凭证泄露到检测处置的完整链路。
4.5 实战中的取舍
关于撞库检测,最后我想分享一点自己的体会。
你可能会有一种冲动,想尽可能多地捕获撞库行为,把阈值设得很紧。但是,你要小心。我见过不少项目,因为检测太敏感,把正常用户的几次密码误输都当成撞库给封号了。你想想看,用户只是忘了密码,多试了几次,结果账号被锁,那个体验有多糟糕。
我的个人原则是:宁可漏掉一次攻击,也不要误封三个正常用户。因为误封会直接导致客服压力上升、用户流失。而防撞库这件事,还有很多人已经在做了——比如大多数风控服务提供商现在都能提供现成的API,你完全可以用第三方数据来辅助判断。我之前在项目里就集成了两个风险情报源,效果还不错。
记住,撞库检测不是为了零误报,而是为了在损失发生前,给安全运营争取到足够的反应时间。