【CTF上級編|第7話/全10話】OAuth/SSOの落とし穴|認可コード窃取とredirect_uri検証バイパス

⚡ CTF上級編|第7話/全10話

OAuth/SSOの落とし穴|認可コード窃取とredirect_uri検証バイパス

第6話では権限の「組み合わせ」が昇格を生む構造を見てきました。第7話は認証・認可の入口そのもの——OAuthに焦点を当てます。「○○でログイン」という便利な仕組みの裏側で、redirect_uriの検証が少し緩いだけで、認可コードがまるごと攻撃者に渡ってしまうことがあります。

🔐 OAuth認可コードフロー 🪤 redirect_uri検証バイパス 🎭 アカウント乗っ取り ⭐ 難易度:★★★★☆
01

🔐「○○でログイン」の裏側|OAuth認可コードフローの基本

ユーザーが許可し、認可コードが橋渡しする仕組み

💡

そのログインボタンを押した瞬間、何が起きているか

「Aurora IDでログイン」ボタンを押すと、ブラウザはまずAurora ID(認可サーバー)に送られ、「このアプリにプロフィール情報へのアクセスを許可しますか?」と確認されます。許可すると、Aurora IDはあらかじめ登録された「redirect_uri」というURLにブラウザを送り返し、そこに「認可コード」という短い文字列を添えます。アプリ側はこの認可コードをアクセストークンと引き換えます。この一連の流れをOAuth 2.0の「認可コードフロー」と呼びます。

認可コードフローには3つの役割が登場します。「ユーザー本人」「ユーザーがログインしたいアプリ(クライアント)」「Aurora IDのような認可サーバー」です。redirect_uriは、認可サーバーが発行した認可コードを「どこに届けるか」を決める、フローの中で最も重要な値の1つです。攻撃者がこのredirect_uriを操作できれば、本来アプリに届くはずの認可コードを自分のサーバーに届けさせることができます。

OAuth認可コードフローの基本 ユーザー Aurora ID 認可サーバー redirect_uriへ認可コードを送る redirect_uri 認可コードの届け先 ToDoループ(クライアント) コードをトークンと交換 redirect_uriが正しければ、ここにしか達しない
📌

この話の前提知識

実践編第3話では、発行されたJWTそのものを偽造する手法を扱いました。今回はトークンを偽造するのではなく、正規の発行プロセスを乗っ取り、本物の認可コードを横取りする手法を扱います。

  • 実践編 第3話「JWTを偽造する|alg:noneと弱い署名鍵」

redirect_uriの検証さえ厳密であれば、この経路は安全に保たれます。次のセクションでは、その検証が緩いとどうなるかを見ていきます。

02

🪤 redirect_uriが少し緩いだけで起きること

「登録したURLと同じか」の判定方法が、安全性を決める

Aurora IDは、アプリを登録する際に「このアプリの認可コードは、このURLにだけ送ってよい」というredirect_uriを登録させます。認可リクエストが来たとき、Aurora IDはリクエストに含まれるredirect_uriが「登録された値と一致するか」を確認します。この確認の実装方法には複数のレベルがあります。

検証方式内容安全性
完全一致リクエストのredirect_uriが登録値と1文字も違わず一致するかを確認安全
前方一致(文字列として)登録値で始まる文字列かどうかだけを確認危険(本話のテーマ)
ホスト名のみ確認ドメイン名の部分だけ一致すればパスは問わない危険な場合がある
正規表現で緩く許可サブドメインをワイルドカードで許可するなど設定次第で危険
⚠️

「前方一致」は実装が簡単なだけに、よく選ばれてしまう

完全一致の実装はシンプルですが、「アプリが複数のサブドメインを使う」「開発環境用のURLも許可したい」といった事情から、文字列としての前方一致やワイルドカードに緩めてしまう実装が後を絶ちません。これはOAuthというプロトコル自体の欠陥ではなく、個々のサービスの実装判断の問題です。実際、複数のセキュリティ研究者やバグバウンティプログラムが、有名サービスを含む多数のOAuth実装でこの種のredirect_uri検証不備を報告しています。

03

🔍 なぜ見つけにくいのか|文字列としての一致とホスト名としての一致

「で始まっている」ことと「同じ場所を指している」ことは別物

「https://todo-app.example」という文字列で始まっていれば安全、と考えるのは自然ですが、URLの世界ではそうとは限りません。ドメイン名は「ラベル」をピリオドで連結した構造になっており、攻撃者が自分の持つドメイン(例:attacker.example)の前に、被害者のドメイン名そのものをサブドメインのラベルとして自由に付け加えることができます。

https://todo-app.example.attacker.example/steal todo-app.example .attacker.example 文字列としてはここで始まる(前方一致は通る) しかし実際のホスト名はこの全体だけ attacker.exampleを所有していれば、その前に付けるラベルは自由 todo-app.exampleという文字列をそのままラベルに使っても問題はない
🧠

文字列比較ではなく、URLとして解析してから比較する

「todo-app.example.attacker.example」は、文字列としては「todo-app.example」を含みますが、URLとして解釈すれば、これは「attacker.example」というドメインの中に作られた、ただ1つのサブドメインです。前方一致の検証は文字列の見た目だけを見ており、実際にどのサーバーへ通信が向かうのかを見ていません。

次の実践チャレンジでは、この食い違いを実際に作り出し、認可コードを横取りする一連の流れを体験します。

04

💻 実践チャレンジ: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を入力して、検証をすり抜けられるか試してください。

🧩 実践チャレンジ:検証をすり抜けるredirect_uriを見つけて認可コードを横取りせよ

ステージ1をクリアすると、ステージ2が解放されます。両方クリアした時点でスコアが確定します。

📊 ステージ進捗: 0/2|挑戦回数: 0回

1ステージ1:検証をすり抜けるredirect_uriを見つける

上のテスターで、Aurora IDの検証は通過するが、実際の遷移先はtodo-app.exampleではない、というredirect_uriを見つけてください。見つけると合言葉が表示されるので、それを入力してください(例: OAUTH-BYPASS-OK)。

第7話の攻撃チェーン ステージ1:検証をすり抜けるURIを発見 前方一致の穴を突く ステージ2:認可コードを横取り 攻撃者のサーバーにコードが届く アクセストークン窃取 被害者になりすませる redirect_uriの検証が唯一の防波堤だった
05

🛡️ 防御側の視点|redirect_uriをどう守るか

「文字列の見た目」でなく「URLとして解析した結果」で判定する

  • redirect_uriは完全一致で検証する:前方一致・部分一致・正規表現による緩い判定は使わない
  • 複数のredirect_uriを許可する場合も、登録リストとの完全一致のみ許可する:ワイルドカードや「サブドメインなら何でも許可」のような設定は避ける
  • stateパラメータでCSRF対策をする:認可リクエストごとに使い捨ての値を発行し、認可コードフロー自体への割り込みを防ぐ
  • PKCE(Proof Key for Code Exchange)を使う:認可コードだけでなく、クライアントが生成した検証用の値も必要にすることで、コードを横取りされても交換できないようにする
  • 認可コードは短命・使い捨てにする:発行から数十秒〜数分で失効させ、1回使われたコードは二度と使えないようにする

文字列としての見た目でなく、URLとして解析した結果のホスト名で比較する

「で始まっているから安全」という直感は、URLの世界では裏切られることがあります。redirect_uriの検証は、必ずURLとして解析した上でホスト名(とできればパス)を完全一致で比較する必要があります。

個々の処理は意図どおりに動いていても、検証の基準が「文字列としての見た目」のままだと、攻撃者にとっての入口が残ります。次のセクションでは今回の学びをまとめます。

06

📝 まとめ+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(あなたの端末内)だけに保存され、外部のサーバーには一切送信されません。

次回・第8話

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)

コメント