OAuth 2.0の「リダイレクトURI」という盲点。認可コード横取りを防ぐための実戦的防衛術
OAuth 2.0やOIDCは、今やWeb開発において避けては通れない認証・認可の標準だ。しかし、多くの開発現場において、このプロトコルは「ライブラリがよしなにやってくれる便利なもの」として扱われがちだ。
だが、現場を知る人間から言わせれば、それは極めて危険な認識だ。認可コードの横取り(Authorization Code Interception)は、設定のわずかな隙を突くだけで、ユーザーのセッションを丸ごと乗っ取れる致命的な脆弱性につながる。今回は、攻撃者がどこを見ており、どう防御すべきかを、現場の視点から紐解いていこう。
—
1. なぜ「リダイレクトURI」が狙われるのか
OAuth 2.0のフローにおいて、認可サーバーは認証成功後、ユーザーを redirect_uri に送り返す。この時、認可コード(codeパラメータ)がクエリ文字列として付与されるわけだが、ここが最大の弱点だ。
攻撃者の視点:なぜコードを盗めるのか
攻撃者は以下のような手口でコードを奪おうとする。
- Open Redirect: アプリケーション側のリダイレクト処理に不備があり、攻撃者が指定した悪意ある外部サイトへユーザーをリダイレクトさせる。
- リダイレクトURIの正規表現の甘さ: 例えば
https://app.example.com.*のように設定されている場合、攻撃者はhttps://app.example.com.evil.comといったドメインを用意し、正規表現をパスして認可コードを自分のサーバーへ飛ばさせる。 - Refererヘッダの漏洩: 認可後のページに外部リソース(画像やスクリプト)が埋め込まれていると、そのリソースへのリクエストに認可コードが含まれたRefererが送信されてしまう。
これらを防ぐための唯一の正解は、「リダイレクトURIの厳密な完全一致(Exact Match)検証」だ。曖昧なマッチングは、セキュリティにおける「死」を意味する。
—
2. 認可コードを守る最後の砦:PKCE(Proof Key for Code Exchange)
かつてはモバイルアプリ特有の対策と思われていたPKCEだが、現在ではWebアプリケーションにおいても必須の「標準装備」だ。
PKCEは、クライアントが認可リクエスト時に code_challenge を送り、トークン交換時に code_verifier を送ることで、「認可コードを受け取る権限があるのは、最初のリクエストを出した本人だけである」ことを証明する仕組みだ。これにより、たとえ中間者攻撃で認可コードを盗聴されたとしても、攻撃者はトークンを入手できない。
—
3. 実装サンプル:PKCEを用いたセキュアな実装
ここでは、フロントエンド(JavaScript)で code_verifier を生成し、バックエンド(Node.js/Express想定)で検証するイメージをコード化する。
フロントエンド(認証開始)
// crypto APIを使ってランダムな文字列を生成
async function generateCodeVerifier() {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return btoa(String.fromCharCode.apply(null, array))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
// SHA-256でハッシュ化してChallengeを作成
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const digest = await window.crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode.apply(null, new Uint8Array(digest)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
バックエンド(コード交換時の検証)
// 認可コードとverifierを受け取り、認可サーバーへ投げる
app.post('/callback', async (req, res) => {
const { code, code_verifier } = req.body;
// 認可サーバーへのトークンリクエスト
const response = await axios.post(TOKEN_ENDPOINT, {
grant_type: 'authorization_code',
code: code,
redirect_uri: 'https://my-app.com/callback',
client_id: CLIENT_ID,
code_verifier: code_verifier // これがPKCEの肝。認可サーバー側で照合される
});
// トークン取得後の処理...
});
—
4. インフラ・設定レベルでの防御
コードだけでなく、設定も引き締めなければならない。
NginxでのリダイレクトURI検証
もしバックエンドの前段にNginxを置いているなら、リダイレクトURIへのリクエストを厳格に制限する設定を入れよう。
# 特定のcallback URI以外へのPOSTリクエストを拒否する設定例
location /oauth/callback {
if ($arg_redirect_uri != "https://my-app.com/callback") {
return 403; # 不正なリダイレクト先は即座に遮断
}
proxy_pass http://backend_server;
}
—
5. 現場の教訓:完璧な防御のために
最後に、現場で事故を防ぐためのチェックリストを置いておく。
1. ホワイトリストは完全一致で: 認可サーバーの設定で、redirect_uri を1文字単位で厳密に指定すること。サブドメインやワイルドカードは論外だ。
2. stateパラメータは必須: stateパラメータを使ってCSRF対策を確実に行うこと。これもよく忘れ去られるが、攻撃者にとっての格好の入り口だ。
3. PKCEを「強制」する: 認可サーバーの設定で、「PKCEなしのリクエストを拒否する」というオプションがあれば、迷わず有効にすること。
セキュリティとは、「便利なものほど、裏側で面倒な確認作業を怠らない」ことに尽きる。ライブラリが何をしてくれているのか、一度ソースコードを追って確認してみてほしい。その「面倒」の先に、顧客の信頼を守るための盾がある。
明日からの開発で、一度 redirect_uri の設定を見直してみる。それが、最強の防御の第一歩だ。
コメント