第四章 撞库攻击与凭证填充检测:凭证泄漏来源、批量尝试特征

说实话,这是我个人认为整个异常登录检测里最“残酷”的一个章节。为什么残酷?因为我见过太多企业,安全建得跟铁桶一样,最后被攻破的地方,居然是用户自己用的“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 用户名的分散与集中

这一点很有意思。攻击者拿到一批泄露的账号后,通常会怎么做?

  1. 集中尝试:用同一个密码,试试这一批用户名能不能登录。比如“password123”配合1000个用户名。
  2. 稀释尝试:怕触发风控,每个用户名只试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. 快速识别:实时统计过去1分钟内,登录失败的 关联对(用户名,IP,密码组合)数量。如果某个用户名关联了超过5个不同IP且失败率 > 80%,立刻标记。
  2. 短时封锁:对确定是撞库的IP,直接加入动态黑名单1小时。注意,不是永久封,因为很多攻击IP是动态的,1小时后可能就换掉了。
  3. 用户提醒:如果某个账号确实被撞成功了,我会建议立刻强制该用户修改密码,并且用二次认证进行验证。我在项目里设定的是:如果从新设备/新地点登录,先发个短信验证码再说。
# 一个简单的撞库检测伪代码
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万次登录事件,延迟只有几毫秒。


本章知识结构图

为了帮你快速建立整体认知,我把本章的核心逻辑画了一张图。这张图展示了从凭证泄露到检测处置的完整链路。

撞库攻击与凭证填充检测知识结构图 凭证泄漏来源 第三方数据泄露 撞库(反向操作) 密码复用(用户习惯) 钓鱼/社会工程学 批量尝试特征 登录频率异常 用户名/密码分布 登录结果分布异常 User-Agent/请求规律 检测与处置 实时失败率统计 (>90% + 高频) 动态IP黑名单 (1小时短时封禁) 强制修改密码 + 二次验证 核心思想:别指望用户不泄露密码,要在登录入口把攻击者堵住 单一特征可能误报,组合使用「高频+高失败率+用户名分散+密码雷同」检出率最高 处置策略要分层:先实时检测,再短时封禁,最后对受害者账号做二次验证

4.5 实战中的取舍

关于撞库检测,最后我想分享一点自己的体会。

你可能会有一种冲动,想尽可能多地捕获撞库行为,把阈值设得很紧。但是,你要小心。我见过不少项目,因为检测太敏感,把正常用户的几次密码误输都当成撞库给封号了。你想想看,用户只是忘了密码,多试了几次,结果账号被锁,那个体验有多糟糕。

我的个人原则是:宁可漏掉一次攻击,也不要误封三个正常用户。因为误封会直接导致客服压力上升、用户流失。而防撞库这件事,还有很多人已经在做了——比如大多数风控服务提供商现在都能提供现成的API,你完全可以用第三方数据来辅助判断。我之前在项目里就集成了两个风险情报源,效果还不错。

记住,撞库检测不是为了零误报,而是为了在损失发生前,给安全运营争取到足够的反应时间。