現場のエンジニア諸君、今日も泥臭いデバッグと戦っていることだろう。
セキュリティの世界では「境界線」がすべてだ。特にOAuth 2.0において、認可コード(Authorization Code)という「鍵」をどこへ届けるか――この配送先であるredirect_uriの管理を甘く見ることは、玄関の鍵を道端に落とすのと同じくらい危険な行為だ。
今日は、多くの開発者が「なんとなく」で実装しがちなredirect_uriのホワイトリスト管理について、現場のインシデント事例を交えて深掘りしていく。
なぜ「部分一致」が地獄への入り口なのか
多くの若手エンジニアは、設定を楽にするためにredirect_uriを「前方一致」で許容しようとする。例えば、https://app.example.com/callback を許可したいがために、https://app.example.com/ から始まるすべてのURLを許可してしまうようなケースだ。
これは、攻撃者にとって「オープンリダイレクタ脆弱性」という極上の餌場になる。
攻撃手法:認可コード強奪(PoC)
もし君のアプリが https://app.example.com/ 以下ならどこでもリダイレクト可能だとしたら、攻撃者は以下のような罠を仕掛ける。
1. 攻撃対象の発見: 君のアプリが脆弱なリダイレクトを受け入れていることを確認する。
2. 罠の設置: 攻撃者が管理するサイトに、君のアプリの「オープンリダイレクタ」機能を持つエンドポイントへのリンクを仕込む。
3. コード強奪:
- 攻撃者は、被害者を偽の認可リクエストへ誘導する。
- 認可サーバーはコードを
https://app.example.com/redirect?url=https://attacker.com/stealのように発行してしまう。 - ブラウザは忠実に攻撃者のサーバーへコードを転送する。
- 攻撃者はそのコードを使い、君のユーザーになりすましてアクセストークンを取得する。
こうなれば、ユーザーの個人情報は丸裸だ。これを防ぐ唯一の解は、「完全一致」でのホワイトリスト管理である。
実装の鉄則:完全一致によるバリデーション
「後からエンドポイントが増えるから……」といった言い訳は通じない。セキュリティにおいて柔軟性は往々にして脆弱性と同義だ。データベースや環境変数で管理するホワイトリストは、必ず一言一句の狂いなく比較しなければならない。
PHPによるセキュアな実装例
PHPで認可サーバー側のバリデーションを行う際、in_arrayを使用して厳密に比較する例を示す。
<?php
/**
* 認可コード送付先のバリデーション関数
* @param string $redirect_uri クライアントから要求されたURI
* @param array $allowed_uris 管理者が設定したホワイトリスト
* @return bool
*/
function validate_redirect_uri(string $redirect_uri, array $allowed_uris): bool {
// 1. URLの構文チェック
if (!filter_var($redirect_uri, FILTER_VALIDATE_URL)) {
return false;
}
// 2. 完全一致での比較 (in_arrayの第3引数で型まで厳密にチェック)
// 比較する際は正規化して比較するのも定石だが、基本は静的リストとの比較が安全
return in_array($redirect_uri, $allowed_uris, true);
}
// 設定例 (環境変数などから読み込むのがベスト)
$allowed_uris = [
"https://app.example.com/oauth/callback",
"https://api.example.com/v1/auth"
];
$requested_uri = $_GET['redirect_uri'] ?? '';
if (!validate_redirect_uri($requested_uri, $allowed_uris)) {
// ここでログを吐く。不正なアクセスを試みたIPを検知対象に含めるべき
error_log("不正なリダイレクト試行: " . $requested_uri);
http_response_code(400);
die("Invalid Redirect URI");
}
インフラ層での防御:Nginxによるガード
コードレベルでの修正が間に合わない場合、あるいは多層防御の一環として、WAFやリバースプロキシで「不審なリダイレクト」を遮断する手もある。しかし、あくまでこれは「保険」だ。
Nginxでredirect_uriのクエリパラメータを厳密に制限するのは難しいが、特定のパス以外へのリダイレクトを禁止する設定は有効だ。
# 特定のOAuthパス以外からのリダイレクトをブロックする例
location /oauth/ {
# クエリパラメータに悪意あるURLが含まれていないかチェック (簡易的)
if ($arg_redirect_uri !~* "^https://app\.example\.com/callback$") {
return 403;
}
proxy_pass http://backend_cluster;
}
最後に:エンジニアとしての心構え
「コードが動くこと」と「コードが安全であること」は全く別のスキルセットだ。
OAuth 2.0の仕様書(RFC 6749)を読めば、redirect_uriの完全一致は「MUST(必須)」とされている。しかし、現場では「検証が面倒だから」「テスト環境では動かしたいから」という理由で、ワイルドカードや部分一致がまかり通っている。
君たちがコードを書く際、その一行が「ユーザーの人生を預かっている」という意識を持ってほしい。redirect_uriの比較において、妥協は攻撃者への招待状だ。
今日からでも自分の書いたOAuth関連のコードを見直してみてくれ。strposやpreg_matchで安易にパスをチェックしていないか? もしそうなら、今すぐ配列による完全一致へ書き換えるべきだ。それが、エンジニアとして信頼されるための第一歩である。
何かあればいつでも相談してくれ。技術は裏切らないが、甘い設計は必ず裏切る。精進するように。
コメント