【実務・中級編】認可サーバーにおけるリダイレクトURIの厳密なホワイトリスト検証 – アプリケーションセキュリティ & 安全な開発防御ガイド

OAuthの「穴」を塞げ:リダイレクトURIの曖昧な検証が招く悪夢

現場でインシデント対応をしていると、「なぜこんな初歩的なミスが?」と頭を抱えたくなる瞬間がある。その筆頭が、OAuth 2.0 / OpenID Connectの認可フローにおける「リダイレクトURIの検証不足」だ。

「面倒だからドメインだけ一致していればいいや」「サブドメインなら許容しよう」……その甘い判断が、攻撃者にとっての黄金の鍵になる。今日は、なぜこの検証が「完全一致」でなければならないのか、その危険なメカニズムと、明日から使える実装の作法を叩き込む。

—

1. なぜ「ゆるい検証」が命取りなのか:攻撃シナリオ

攻撃者は、認可サーバーがリダイレクトURIを「前方一致」や「正規表現」で判定している隙を突く。

例えば、https://app.example.com/callback が正規のURIだとしよう。もしサーバー側のバリデーションが startsWith のような甘い実装であれば、攻撃者は以下のようなURLを作成する。

https://app.example.com.attacker.com/callback

これを認可サーバーに投げると、サーバーは「https://app.example.com で始まっているからOK」と判断し、認可コードを攻撃者の管理するサーバーへ送信してしまう。これが「認可コード流出(Authorization Code Interception)」だ。

攻撃の手順(PoC)

1. 罠の設置: 攻撃者は app.example.com.attacker.com を取得し、ログ収集用のスクリプトを仕込む。
2. 誘導: 被害者に細工した認可URL(redirect_uriを上記URLに変更したもの)をクリックさせる。
3. 奪取: 認可サーバーが認可コードを攻撃者のサーバーへリダイレクト。
4. 乗っ取り: 攻撃者がそのコードを使ってアクセストークンを取得し、被害者のアカウントを操作する。

正規表現で ^https://.\.example\.com と書くのは、セキュリティの自殺行為に近い。サブドメイン配下にXSS脆弱性があったり、オープンリダイレクトが存在したりすれば、そこがすべて突破口になるからだ。

—

2. セキュアな実装:完全一致(Exact Match)の鉄則

検証ロジックはシンプルであるべきだ。「リストに存在するか」を「完全一致」で確認する。これ以外の選択肢はない。

Python (Flask) による実装例

「リストに存在するか」を定数として持ち、in 演算子で確認する。

安全なリダイレクトURIのホワイトリスト
ALLOWED_REDIRECT_URIS = [
“https://app.example.com/callback”,
“https://mobile.example.com/auth”
]

def validate_redirect_uri(redirect_uri):
# パラメータのバリデーションは常に完全一致で行う
if redirect_uri not in ALLOWED_REDIRECT_URIS:
# ログを出力して、即座に例外を投げる
logger.warning(f”不正なリダイレクトURIが検出されました: {redirect_uri}”)
raise ValueError(“Invalid redirect_uri”)

return True

JavaScript (Node.js/Express) による実装例

const ALLOWED_URIS = new Set([
“https://app.example.com/callback”,
“https://mobile.example.com/auth”
]);

function validateRedirectUri(uri) {
// Setオブジェクトを使うことで、検索の計算量をO(1)に抑える
if (!ALLOWED_URIS.has(uri)) {
throw new Error(“Security Alert: Unauthorized redirect URI attempt”);
}
return true;
}

—

3. インフラ・設定レベルでの防御(Nginx/WAF)

アプリケーション側で防ぐのが基本だが、多層防御としてインフラ側でも制御しておくべきだ。

Nginxでの検証

もし認可サーバーの入り口がNginxなら、mapモジュールを使って不正なリダイレクトをブロックするのも有効な手段だ。

nginx.conf
map $arg_redirect_uri $is_allowed_uri {
default 0;
“https://app.example.com/callback” 1;
“https://mobile.example.com/auth” 1;
}

server {
location /oauth/authorize {
if ($is_allowed_uri = 0) {
return 403 “Forbidden: Invalid redirect_uri”;
}
# 通過させる設定
proxy_pass http://backend_app;
}
}

—

4. プロのチェックリスト:開発者が忘れてはいけないこと

コードを書くとき、以下の3点を常に自問自答してほしい。

1. ワイルドカードは悪魔: .example.com のような設定は、いかなる場合も許可してはならない。どうしても必要な場合は、クライアントごとに厳格な設定をデータベースで管理する。
2. クエリパラメータの無視: リダイレクトURIの検証時、後ろに付くクエリパラメータ(?state=...など)を含めて検証するか、あるいは認可サーバーとして「リダイレクトURIに動的なクエリを含めることを禁止」するポリシーを採用すべきだ。
3. エラーハンドリングの徹底: 不正なリダイレクトURIが飛んできたとき、エラー画面でURIを表示しないこと。攻撃者に「どの文字列なら通るか」というヒントを与えてしまうためだ。

セキュリティは、派手なハッキング技術を止めることではなく、「当たり前のことを当たり前にやり続ける」という泥臭い積み重ねで成り立っている。この「リダイレクトURIの完全一致」という鉄則を、チームの標準設定として今日から徹底してほしい。

次にコードレビューをする際、if (uri.startsWith(...)) を見つけたら、容赦なく「拒絶」してくれ。それが、君の書くシステムを世界一安全にする第一歩だ。

コメント

タイトルとURLをコピーしました