OAuth/SSOの落とし穴|認可コード窃取とredirect_uri検証バイパス
第6話では権限の「組み合わせ」が昇格を生む構造を見てきました。第7話は認証・認可の入口そのもの——OAuthに焦点を当てます。「○○でログイン」という便利な仕組みの裏側で、redirect_uriの検証が少し緩いだけで、認可コードがまるごと攻撃者に渡ってしまうことがあります。
📋 目次
🔐「○○でログイン」の裏側|OAuth認可コードフローの基本
ユーザーが許可し、認可コードが橋渡しする仕組み
そのログインボタンを押した瞬間、何が起きているか
「Aurora IDでログイン」ボタンを押すと、ブラウザはまずAurora ID(認可サーバー)に送られ、「このアプリにプロフィール情報へのアクセスを許可しますか?」と確認されます。許可すると、Aurora IDはあらかじめ登録された「redirect_uri」というURLにブラウザを送り返し、そこに「認可コード」という短い文字列を添えます。アプリ側はこの認可コードをアクセストークンと引き換えます。この一連の流れをOAuth 2.0の「認可コードフロー」と呼びます。
認可コードフローには3つの役割が登場します。「ユーザー本人」「ユーザーがログインしたいアプリ(クライアント)」「Aurora IDのような認可サーバー」です。redirect_uriは、認可サーバーが発行した認可コードを「どこに届けるか」を決める、フローの中で最も重要な値の1つです。攻撃者がこのredirect_uriを操作できれば、本来アプリに届くはずの認可コードを自分のサーバーに届けさせることができます。
この話の前提知識
実践編第3話では、発行されたJWTそのものを偽造する手法を扱いました。今回はトークンを偽造するのではなく、正規の発行プロセスを乗っ取り、本物の認可コードを横取りする手法を扱います。
- 実践編 第3話「JWTを偽造する|alg:noneと弱い署名鍵」
redirect_uriの検証さえ厳密であれば、この経路は安全に保たれます。次のセクションでは、その検証が緩いとどうなるかを見ていきます。
🪤 redirect_uriが少し緩いだけで起きること
「登録したURLと同じか」の判定方法が、安全性を決める
Aurora IDは、アプリを登録する際に「このアプリの認可コードは、このURLにだけ送ってよい」というredirect_uriを登録させます。認可リクエストが来たとき、Aurora IDはリクエストに含まれるredirect_uriが「登録された値と一致するか」を確認します。この確認の実装方法には複数のレベルがあります。
| 検証方式 | 内容 | 安全性 |
|---|---|---|
| 完全一致 | リクエストのredirect_uriが登録値と1文字も違わず一致するかを確認 | 安全 |
| 前方一致(文字列として) | 登録値で始まる文字列かどうかだけを確認 | 危険(本話のテーマ) |
| ホスト名のみ確認 | ドメイン名の部分だけ一致すればパスは問わない | 危険な場合がある |
| 正規表現で緩く許可 | サブドメインをワイルドカードで許可するなど | 設定次第で危険 |
「前方一致」は実装が簡単なだけに、よく選ばれてしまう
完全一致の実装はシンプルですが、「アプリが複数のサブドメインを使う」「開発環境用のURLも許可したい」といった事情から、文字列としての前方一致やワイルドカードに緩めてしまう実装が後を絶ちません。これはOAuthというプロトコル自体の欠陥ではなく、個々のサービスの実装判断の問題です。実際、複数のセキュリティ研究者やバグバウンティプログラムが、有名サービスを含む多数のOAuth実装でこの種のredirect_uri検証不備を報告しています。
🔍 なぜ見つけにくいのか|文字列としての一致とホスト名としての一致
「で始まっている」ことと「同じ場所を指している」ことは別物
「https://todo-app.example」という文字列で始まっていれば安全、と考えるのは自然ですが、URLの世界ではそうとは限りません。ドメイン名は「ラベル」をピリオドで連結した構造になっており、攻撃者が自分の持つドメイン(例:attacker.example)の前に、被害者のドメイン名そのものをサブドメインのラベルとして自由に付け加えることができます。
文字列比較ではなく、URLとして解析してから比較する
「todo-app.example.attacker.example」は、文字列としては「todo-app.example」を含みますが、URLとして解釈すれば、これは「attacker.example」というドメインの中に作られた、ただ1つのサブドメインです。前方一致の検証は文字列の見た目だけを見ており、実際にどのサーバーへ通信が向かうのかを見ていません。
次の実践チャレンジでは、この食い違いを実際に作り出し、認可コードを横取りする一連の流れを体験します。
💻 実践チャレンジ:Aurora IDのredirect_uri検証を突破せよ
登録済みURLを通過する、本物とは違う行き先を作る
Aurora IDに登録されているToDoループ(架空のToDoアプリ)のredirect_uriは、次の通りです。Aurora ID側の検証コードも見ることができます。よく見ると、検証は「文字列の先頭が一致するか」しか見ていません。
$ aurora-cli oauth describe-client –client-id todoloop-prod
registered redirect_uri: https://todo-app.example/callback
// Aurora ID側の検証コード(バグあり)
function naiveValidate(uri) {
return uri.indexOf('https://todo-app.example') === 0;
}
下に候補のredirect_uriを入力して、検証をすり抜けられるか試してください。
ステージ1をクリアすると、ステージ2が解放されます。両方クリアした時点でスコアが確定します。
📊 ステージ進捗: 0/2|挑戦回数: 0回
上のテスターで、Aurora IDの検証は通過するが、実際の遷移先はtodo-app.exampleではない、というredirect_uriを見つけてください。見つけると合言葉が表示されるので、それを入力してください(例: OAUTH-BYPASS-OK)。
検証コードをよく見るとindexOf(...) === 0、つまり「文字列の先頭が一致するか」しか確認していません。これは「同じ場所を指しているか」の確認とは違います。
ドメイン名は前から後ろへラベルをピリオドでつなぐ構造です。todo-app.exampleという文字列をそのまま使い、その後ろに自分の持つドメイン(例:.attacker.example)を続けても、文字列としては「todo-app.exampleで始まる」ままです。
見つけたredirect_uriを使って、下の「認可リクエストを送信する」ボタンを押すと、一連の流れが実行されます。最後に表示されたパネルの中の文字列をそのまま入力してください。
🚨 victim@todo-app.example のアクセストークンを取得しました
access_token: at_7e2f9c14...(略)
会員専用ページで発見したフラグ: CTF{0AUTH_C0DE_HIJACK}
ステージ1で見つけた、検証をすり抜けるredirect_uriを使って「認可リクエストを送信する」ボタンを押すと、一連の流れが自動的に実行されます。最後に表示されるパネルの中の文字列をそのまま入力してください。
🛡️ 防御側の視点|redirect_uriをどう守るか
「文字列の見た目」でなく「URLとして解析した結果」で判定する
- redirect_uriは完全一致で検証する:前方一致・部分一致・正規表現による緩い判定は使わない
- 複数のredirect_uriを許可する場合も、登録リストとの完全一致のみ許可する:ワイルドカードや「サブドメインなら何でも許可」のような設定は避ける
- stateパラメータでCSRF対策をする:認可リクエストごとに使い捨ての値を発行し、認可コードフロー自体への割り込みを防ぐ
- PKCE(Proof Key for Code Exchange)を使う:認可コードだけでなく、クライアントが生成した検証用の値も必要にすることで、コードを横取りされても交換できないようにする
- 認可コードは短命・使い捨てにする:発行から数十秒〜数分で失効させ、1回使われたコードは二度と使えないようにする
文字列としての見た目でなく、URLとして解析した結果のホスト名で比較する
「で始まっているから安全」という直感は、URLの世界では裏切られることがあります。redirect_uriの検証は、必ずURLとして解析した上でホスト名(とできればパス)を完全一致で比較する必要があります。
個々の処理は意図どおりに動いていても、検証の基準が「文字列としての見た目」のままだと、攻撃者にとっての入口が残ります。次のセクションでは今回の学びをまとめます。
📝 まとめ+FAQ+次回予告
「で始まる」ことと「そこに行く」ことは違う
第7話では、OAuth認可コードフローのredirect_uri検証が前方一致のように緩いと、認可コードそのものが攻撃者に渡ってしまう脆弱性クラスを体験しました。CTFの「フラグ探し」ではなく、実際の脆弱性レポートで繰り返し報告されている構造そのものです。
・OAuth認可コードフローでは、redirect_uriが認可コードの届け先を決める
・redirect_uriの検証が前方一致のように緩いと、サブドメインを使って検証をすり抜けられる
・文字列としての一致と、URLとして解析した結果のホスト名の一致は別物
・防御の鍵は完全一致での検証と、PKCE・stateパラメータの併用
Q. 実際のOAuthサービスでもこうした検証不備は起きていますか?
はい。複数のセキュリティ研究者やバグバウンティプログラムが、有名サービスを含む多数のOAuth実装でredirect_uriの検証不備を報告しています。本話の「前方一致による検証」という構造は、よく知られた手法の1つを学習用に再構成したものです。
Q. なぜ実在のOAuthプロバイダの名称をそのまま使わないのですか?
実在のサービスの名称をそのまま使うと、本物のサービスの脆弱性の解説と誤認されるおそれがあるためです。本話では概念と構造を正確に保ったまま、架空の名称・架空のAPI形式に置き換えています。
Q. このシミュレーターは実際のOAuthサーバーに接続していますか?
いいえ。認可エンドポイント・トークンエンドポイントの両方をこのページ内だけで完結するJavaScriptの関数として実装しており、外部のOAuthサービスやネットワーク通信には一切接続していません。
Q. 進捗やスコアの情報はどこかに送信されますか?
送信されません。すべてブラウザのlocalStorage(あなたの端末内)だけに保存され、外部のサーバーには一切送信されません。
XXEで内部ファイルを読み取る|XML外部エンティティの悪用
今回学んだ「外部から渡された値をどこまで信じるか」というテーマを、XMLパーサーの世界に広げます。外部エンティティの解決という仕組みが、どのようにファイル読み取りに発展するかを体験します。
📚 参考情報
- OWASP Top 10(A01:2021 Broken Access Control)
- CWE-601(URL Redirection to Untrusted Site, Open Redirect)
- RFC 9700(OAuth 2.0 Security Best Current Practice)

コメント