PortSwigger 暴力破解实验全通关

本文记录了 PortSwigger Web Security Academy 中 Authentication(基于密码的身份认证) 模块下全部 6 个暴力破解实验的通关思路、Payload 配置、爆破技巧以及对应的修复建议。

配套靶场链接:https://portswigger.net/web-security/authentication/password-based

写在前面

暴力破解(Brute Force)虽然是最”笨”的攻击方式,但放到真实业务里却往往是最容易被忽视、最容易得逞的攻击手段之一。这 6 个实验层层递进,从最简单的”通过差异化响应枚举用户名”,一直到”用响应时间侧信道绕过登录限速”、“通过单个请求携带上千组凭据”等高级技巧,几乎覆盖了 OWASP 中认证类漏洞的所有典型场景。

实验环境统一使用 Burp Suite Community Edition + 自带 username/password 字典,所有爆破结果均通过观察 HTTP 响应差异(状态码、响应长度、响应时间)来识别。

不过其实我用的是yakit


实验一:Username enumeration via different responses

靶场:Username enumeration via different responses

难度:Apprentice | 不提供 wiener:peter 凭据 | 字典:Candidate usernamesCandidate passwords

漏洞本质:登录失败时,服务器对”用户名不存在”和”密码错误”返回了完全不同的响应内容(通常前者更短或文案不同),从而允许攻击者逐条枚举出有效用户名。

攻击流程

1)抓包并加载字典

打开实验页面,在登录表单里随便填一个账号密码登录,用 Burp Suite 拦截请求。然后把请求发送到 Intruder,将 username 字段标记为 payload 位置,加载 usernames.txt 字典。

实验一:加载用户名字典

2)配置 Payload 并发起攻击

Payload type 选择 Simple list,加载候选人名字典后发起攻击。

实验一:配置 Payload

3)观察响应差异

攻击过程中重点观察 Length 列 —— 由于无效用户名和有效用户名(但密码错误)返回的提示文案不同,响应长度会出现明显差异。有效用户名的响应通常更长(因为会多说一句”密码错误”之类的)。

实验一:观察响应长度差异

4)锁定用户名

在结果里很快就能看到一个长度特别突出的响应,它对应的就是有效用户名。

实验一:找到有效用户名 alterwind

5)二次爆破密码

确认用户名为 alterwind 后,再次使用 Intruder,加载 passwords.txt 字典,单独爆破密码。

6)登录验证

得到最终的凭据:

username = alterwind
password = andrew

回到登录页用这对凭据登录,发现页面跳转到欢迎页,证明爆破成功。

实验一:登录成功

防御建议

  • 统一登录失败响应文案,例如统一返回 "用户名或密码错误",不要区分两种情况。
  • 对登录接口做统一的速率限制验证码机制。

实验二:Username enumeration via subtly different responses

靶场:Username enumeration via subtly different responses

难度:Practitioner | 不提供 wiener:peter 凭据 | 字典:Candidate usernamesCandidate passwords

漏洞本质:相比实验一,本关把响应差异化做得更”微妙”,攻击者需要仔细对比响应内容才能找到有效用户名(往往只有一个字母的差异,例如多了个 . 或单词拼写不一致)。

攻击流程

1)观察响应差异

第一次爆破时,乍一看所有响应长度都一样,无法直接通过 Length 列定位有效用户。但当我们主动打开具体某个响应的 Body仔细比对时,会发现其中一条响应的文案与其他略有不同(例如多了一个标点、单词拼写差异等)。

实验二:复制字典

2)拦截登录请求

按常规流程登录一次,抓取 POST /login 的请求包。

实验二:抓包确认字典已加载

3)标记 Payload 位置并爆破

将请求发送到 Intruder,仅对 username 字段标记 payload,加载字典后开始攻击。

实验二:标记 payload 位置

4)仔细比对响应内容

攻击结果中 Length 列几乎一致,这时就要逐条打开响应体进行对比。这里靠 Burp 的 Render / Response 视图切换,找到那条文案”细微不同”的响应。

实验二:发起攻击

5)锁定有效用户名

打开响应内容仔细看就能发现,与其他响应相比某一条的内容里多了一个点号或者单词拼写不同,这就是有效用户名。

实验二:响应细微差异对比

6)二次爆破密码

拿到用户名后,再次用密码字典进行二次爆破,得到正确密码后页面跳转成功。

实验二:登录跳转成功

7)完成实验

实验二:通关成功

防御建议

  • 即使只差一个标点,也要避免在错误信息里泄露任何”用户存在”的信号。
  • 错误响应文案最好从一组预先准备好的固定字符串池里随机抽取返回。
  • 引入 CAPTCHA、登录失败计数等二次校验。

实验三:Username enumeration via response timing

靶场:Username enumeration via response timing

难度:Practitioner | 已知凭据:wiener:peter | 凭据字典:Candidate usernamesCandidate passwords

漏洞本质:服务器在密码校验失败时会对有效用户名执行完整的密码哈希(这类哈希算法对长度敏感,密码越长耗时长),而对无效用户名直接快速返回”用户名错误”。这就构成了响应时间侧信道(timing side-channel):通过观察响应时间差异,可以枚举出真实存在的用户名。本关同时会校验 X-Forwarded-For,用于做 IP 维度的速率限制。

攻击流程

1)抓包并对比响应时间

登录页面随便输入一组凭据并提交,Burp 拦截到登录请求。将请求发送到 Repeater(不是 Intruder)以便对比响应时间。

实验三:抓包并发送至 Repeater

先用正确凭据 wiener:peter 验证登录成功

  • 在 Repeater 中发送一次正确凭据,记下响应时间(约 ~377ms)。
  • 再发送一次完全错误的凭据(如 admin:admin),记下响应时间(约 ~385ms)。
  • 两者差异非常小,肉眼几乎看不出差别。

这时就要用上 Burp 文档里提到的关键技巧:用极长的密码放大哈希耗时

2)用长密码放大响应时间差

wiener:peter 的密码替换为 50 个以上字符的随机字符串(比如 50 个 a),再次发送:

  • 响应时间暴涨到 ~1155ms,是正常登录的 3 倍多。

再用同样的长密码组合一个无效的用户名(如 admin)发送

  • 响应时间仍然只有 ~337ms

这就证明了:服务器只在”用户名有效”时才执行密码哈希,且这个哈希过程会随着密码长度线性增加耗时。这个时间差异就是爆破的依据。

3)添加 X-Forwarded-For 绕过限速

但简单重复发送请求很快会被 IP 限速拦截。解决方案:在请求头里手动加一行 X-Forwarded-For,每次发送都用一个不同的随机 IP。

X-Forwarded-For: §    随机的 IP§

实验三:在 Repeater 中加 X-Forwarded-For 绕过限速

加上 XFF 后再用 wiener + 长密码 发送,响应时间仍然能稳定保持 ~1000ms 以上,限速被绕过了。

4)用 Intruder 发起用户名爆破

现在把请求发送到 Intruder,配置如下:

  • Attack type:Pitchfork(必须用 Pitchfork,让两个 Payload 集按位置一一配对)
  • Payload set 1:X-Forwarded-For → 类型 Numbers,From 1,To 101
  • Payload set 2:username → 类型 Simple list,加载 Candidate usernames 字典
  • 把请求体里的 password 字段保留为那个长字符串(关键!否则时间差异会很小)
  • 并发数 1

实验三:Intruder 配置 Pitchfork + XFF + 用户名

5)观察响应时间锁定有效用户名

发起攻击后观察 Response received / Time 列。大部分请求都在 ~500ms 以内,只有一个请求的响应时间显著偏长(接近 1000ms 甚至更多)。

实验三:响应时间异常偏长的请求

这个用户名就是真实存在的用户(PortSwigger 每次启动实例时目标用户名都不同,本次实例通过响应时间差异爆破出的用户名记在原笔记里,但笔记中未明确写出具体值)。

6)爆破密码

锁定用户名后,再次用 Intruder 爆破密码:

  • Attack type:Pitchfork
  • Payload set 1:X-Forwarded-For → Numbers,200–301
  • Payload set 2:password → Simple list,加载 Candidate passwords
  • username 字段固定为上一步得到的有效用户名
  • password 字段可以保持为正常长度(不再需要长密码放大时间)

实验三:爆破密码的 Intruder 配置

7)登录验证

爆破成功后,正确的请求会返回 302 Found 重定向到账户页面。用得到的凭据在登录页(记得打开 Burp 拦截手动加上 XFF)完成登录。

8)完成实验

实验三:通关成功

防御建议

  • 不要使用响应时间差异作为”用户名是否有效”的副作用。即便逻辑上必须对密码做哈希,也应先判断用户名是否存在,存在时才执行哈希,并配合数据库索引优化,避免响应时间差过大。
  • 严禁信任客户端传入的 X-Forwarded-For 用于登录限速。限速逻辑应该在应用层基于用户、会话或真实客户端 IP 实现。
  • 即便需要哈希密码,也应使用恒定时间的算法(如 Argon2id、bcrypt 的 cost 可调),并对无效用户名也执行一次占位哈希,让响应时间差异收敛到不可测量的程度。

防御建议

  • 不要使用响应时间差异作为”用户名是否有效”的副作用。即便逻辑上必须对密码做哈希,也应先判断用户名是否存在,存在时才执行哈希,并配合数据库索引优化,避免响应时间差过大。
  • 严禁信任客户端传入的 X-Forwarded-For 用于登录限速。限速逻辑应该在应用层基于用户、会话或真实客户端 IP 实现。

实验四:Broken brute-force protection, IP block

靶场:Broken brute-force protection, IP block

难度:Practitioner | 已知凭据:wiener:peter | 目标用户:carlos | 字典:Candidate passwords

漏洞本质:登录接口基于 IP 失败计数 来封锁暴力破解,但该计数器在同一会话内只要有一次成功登录就会被重置。如果攻击者先用一个已知正确的账号(题目提供的 wiener:peter)穿插在爆破序列中,每隔几次就成功登录一次,失败计数器就会被归零,从而可以无限次尝试 carlos 的密码。

攻击流程

1)分析登录流程

观察登录表单:用 wiener / peter 可以正常登录成功;目标用户是 carlos,密码未知。服务器对来自同一 IP 的连续 3 次失败登录会封锁 IP。

实验四:初始登录页

2)构造穿插字典

思路:每隔 2 次 carlos 的尝试,就穿插一次 wiener / peter 的成功登录,从而周期性重置失败计数器。

生成两份这样的字典:

Username 字典

carlos
carlos
wiener
carlos
carlos
wiener
... (    后续按 carlos / carlos / wiener 的规律循环)

Password 字典

123456
password
peter
12345678
qwerty
peter
... (    后续按 真实密码 / 错误密码 / peter 的规律循环)

完整字典结构(这里只列出前几条示意,完整字典按上述规律扩展到目标长度):

usernames.txt:
carlos
carlos
wiener
carlos
carlos
wiener
... (    重复 carlos / carlos / wiener 模式 100 次)

passwords.txt:
123456
password
peter
12345678
qwerty
peter
... (    重复 真实密码 / 错误密码 / peter 模式 100 次)

3)使用 Intruder 发起爆破

将请求发送到 Intruder,标记 usernamepassword 两个位置为 payload,分别加载上面构造的两份字典,Payload count 必须对齐(否则会把 wiener 和 peter 错位配对)。设置并发数为 1。

4)观察爆破结果

攻击进行一段时间后翻看结果,会发现 Status302(登录成功)的响应里就藏着 carlos 的正确密码。点击 302 响应查看 Location 头,可以确认是成功登录的跳转。

实验四:爆破成功找到 carlos 的密码

5)登录验证

用得到的凭据登录:

username = carlos
password = pass

实验四:登录 carlos 成功

实验通关!

防御建议

  • 失败计数应该基于 账号 + IP 组合,而不是单纯依赖 IP。
  • 限制策略不能被”成功登录”无条件重置,应当仅针对当前登录用户重置。
  • 加入 CAPTCHA、二步验证等机制。

实验五:Username enumeration via account lock

靶场:Username enumeration via account lock

难度:Practitioner | 不提供 wiener:peter 凭据 | 字典:Candidate usernamesCandidate passwords

漏洞本质:当某个账号连续登录失败超过阈值(本关为 5 次)后会被锁定,登录接口返回特殊的错误提示(例如 "You have made too many incorrect login attempts. Please try again in 1 minute.")。攻击者利用这一点:先用很小的密码集(不超过锁定阈值)对每个候选用户名各尝试一次,当遍历完整个用户名字典后,真实存在的用户名会因为累计了 5 次失败而进入锁定状态,返回与其他用户名不同的响应长度——这就是枚举用户名的依据。

攻击流程

1)抓包并观察

打开实验页面,抓取登录请求。直接尝试登录几次,可以观察到正常失败时返回”Invalid username or password”,多次失败后会出现”You have made too many incorrect login attempts”的锁定提示。

实验五:抓取 POST /login 请求

2)用 Cluster Bomb 触发账户锁定差异

把请求发送到 Intruder,按以下方式配置:

  • Attack type:Cluster Bomb(让两个 payload 集做笛卡尔积)
  • Payload set 1 (username):Simple list,加载 Candidate usernames 字典
  • Payload set 2 (password):Simple list,只填 5 个任意相同的字符(如 ["a","b","c","d","e"] 即可)

这里 password 的字典大小必须不超过锁定阈值(5),否则一旦用户被锁定后,多出来的密码再尝试反而会导致响应都被识别为锁定状态,扰乱判断。

实验五:Intruder Cluster Bomb 配置

3)发起攻击并观察响应差异

发起攻击后观察每一条响应:

  • 对于无效用户名:5 次错误都返回”Invalid username or password”,响应内容相对稳定。
  • 对于有效用户名:连续 5 次错误后第 5 次返回锁定提示,响应长度会出现明显差异(通常比无效用户名的响应更长)。

实验五:响应长度异常的请求

在结果列表里找到那条响应长度与其他明显不同的请求,对应的就是真实存在的用户名。本次实例爆破出的用户名为 mysql

4)爆破密码

锁定用户名后,再发起一次爆破:

  • Attack type:Sniper
  • Payload 标记:password 字段
  • Payload type:Simple list,加载 Candidate passwords
  • username 字段固定为 mysql

为方便筛选结果,可以在 Options 里添加一个 Grep - Extract 规则,提取响应里的错误消息(如 “Incorrect password”),这样正确密码对应的响应就不会出现该错误,能直接定位。

实验五:Sniper 爆破密码并加 grep 规则

正确凭据对应的响应会是 302 Found,跳转至账户页面。本次实例爆破得到的密码为 123321,最终凭据为:

username = mysql
password = 123321

5)登录验证

用得到的凭据在登录页提交,账号锁定状态可能还没解除——稍等 1 分钟(或者用原笔记中的小技巧多次尝试),即可登录成功。

6)完成实验

页面跳转到账户主页,实验通关。

防御建议

  • 锁定策略应当基于账号 + IP + 时间窗口组合,而不是单纯基于账号连续失败次数。
  • 锁定后无论用户名是否真实存在,都返回完全相同的提示文案
  • 设置合理的锁定阈值(一般 5~10 次失败后再锁定),避免被攻击者利用为枚举侧信道。
  • 提供基于验证码、邮箱验证等更安全的恢复流程,而不是简单的”等待解锁”。

实验六:Broken brute-force protection, multiple credentials per request

靶场:Broken brute-force protection, multiple credentials per request

难度:Expert | 目标用户:carlos | 不提供 wiener:peter 凭据 | 字典:Candidate passwords

漏洞本质:登录接口对请求体做 JSON 解析,没有严格校验 password 字段的数据类型——它接受字符串,也接受数组。后端代码用类似 for pwd in password: if pwd == correct_password 的写法遍历比对,导致只要数组中有一个元素匹配即可登录成功。同时服务器基于”每个请求”做 IP 失败计数(而不是每个凭据),所以一次请求携带上千个候选密码也只算一次失败。

攻击流程

1)抓取 JSON 格式的登录请求

打开 Burp 代理,正常登录一次。观察请求体可以发现:本关的登录请求不是表单,而是 JSON:

POST /login HTTP/1.1
Content-Type: application/json

{
  "username": "carlos",
  "password": "monkey"
}

把请求发送到 Repeater

实验六:Repeater 中查看 JSON 请求体

2)将整个密码字典塞入 JSON 数组

在 Repeater 中,把 password 字段替换为整个候选密码字典构成的 JSON 数组。例如:

{
  "username": "carlos",
  "password": [
    "123456", "password", "12345678", "qwerty",
    "123456789", "12345", "1234", "111111",
    "1234567", "dragon", "123123", "baseball",
    /* ... 整个 Candidate passwords 字典 ... */
    "ashley"
  ]
}

JSON 数组必须严格符合语法:元素用 , 分隔,整个字典放在 [ ... ] 里。整个字典可能有一百多个密码,全部放在一个请求里也无所谓——服务端会依次尝试,匹配到正确密码就返回成功。

实验六:JSON password 字段被替换为整个字典的数组

点击 Send。

3)观察响应

如果数组里正好有正确的密码,服务器会返回 302 Found,把 Location 头设为 /my-account 之类的成功路径,并设置 session cookie。注意:因为 Repeater 的视图里没有渲染跳转,所以表面上看起来响应内容很短,但 Status 已经是 302,同时 Headers 里能看到 Set-Cookie: session=...——这就是登录成功的标志。

实验六:302 重定向响应说明登录成功

4)通过浏览器完成登录

直接在 Repeater 里点右键 → Show response in browser,复制生成的 URL,在浏览器里打开。这样浏览器会带上服务器刚刚签发的 session cookie,自动登录到 carlos 的账户页面。

本次实例的最终凭据为:

{
  "username": "carlos",
  "password": "ashley"
}

实验六:通过 Show response in browser 跳转到登录后的页面

5)所有暴力破解实验全部通关 🎉

账户页面正常打开,实验通关。打开账户页面确认即可完成全部 6 关。

实验六:暴力破解实验全部通关

防御建议

  • 严格校验请求体的 schema:用户名和密码字段必须是字符串类型,一旦收到数组直接拒绝(HTTP 400)。
  • 即使业务上支持”批量登录”场景,也必须为该场景单独设计 API,并加上严格的速率限制账号级失败计数

实战总结与防御清单

把 6 个实验放在一起看,可以总结出暴力破解类漏洞的三大根因

根因对应实验防御手段
响应差异化泄露用户名实验一、实验二、实验五统一错误文案,文案从字符串池随机抽取
侧信道泄露(响应时间/IP 头)实验三不信任 X-Forwarded-For,避免密码哈希耗时差异
限速策略被绕过实验四、实验六基于账号+IP+会话的复合限速;严格 schema 校验

通用的认证加固清单

  1. 密码策略:强制 8 位以上、混合字符、加入常见弱密码字典校验。
  2. 账号锁定:基于账号 + IP + 时间窗口的复合策略,阈值建议 5~10 次。
  3. 限速:登录接口全局 QPS 限制,单 IP 单账号双重计数。
  4. 多因子认证:重要业务必须开启 2FA(短信 / TOTP / 邮件验证码)。
  5. 响应统一:登录失败、账号锁定、密码错误等场景返回完全相同的响应体。
  6. 请求校验:对登录接口的请求体做严格 JSON Schema 校验,拒绝任何不符合预期类型的输入。
  7. 审计与告警:对异常的登录失败聚集、IP 切换、UA 漂移等行为做实时告警。

写在最后:这 6 个实验虽然简单,但每一个都对应着真实世界中曾出现过的认证漏洞。建议把每一关都至少通关两次:第一次看提示通关,第二次关掉所有提示独立完成。只有真正理解了”为什么会成功”,才能在代码评审、安全测试中识别同类问题。

#PortSwigger #BurpSuite #暴力破解 #Web安全