説明
クライアント側のオープンリダイレクトは、クエリ、フラグメント、DOMの値など、ユーザーが制御する値を location.assign、replace、href に直接渡す場合に発生します。外部の宛先を検証せずに許可すると、信頼されたサイトから攻撃者のサイトへ誘導され、フィッシングやトークンの窃取につながるおそれがあります。例えば、操作された to の値によって、ページの読み込み後に外部サイトへ自動で移動する場合があります。
想定される影響
- フィッシングサイトで認証情報やMFAコードを入力させられるおそれがあります。
- オープンリダイレクトを組み合わせてOAuthのリダイレクト先検証を回避され、認可コードやアクセストークンが漏えいする可能性があります。
- 信頼されたリンクから悪意のあるダウンロード先へ誘導され、サービスへの信頼が損なわれるおそれがあります。
- リファラーに基づくフィルターや宛先検証を回避される可能性があります。
対処方法
- ユーザー入力をリダイレクト先として直接使わないでください。
to=homeなどの許可したキーを内部パスへ対応付け、未登録の値は既定のパスへ送ってください。- 内部の宛先だけを許可し、スキームやホストを持つ絶対URL、
//evil.comなどのプロトコル相対URLを拒否してください。 new URL(value, location.origin)で解析する場合は、結果のoriginが現在のサイトと一致し、pathnameが/で始まることを確認してください。- サーバーで最終的なリダイレクトを行う場合も、同じ許可リストを適用してください。
例
変更前
javascript
// フラグメントを外部へのリダイレクト先に直接使用
function unsafeRedirect() {
// URL: https://trusted.example/app#to=https://evil.example
const raw = location.hash.replace(/^#to=/, '');
if (raw) {
// 入力が絶対URLなら外部サイトへ移動する可能性
top.location = decodeURIComponent(raw);
}
}
変更後
javascript
// 許可したキーを内部のリダイレクト先へ対応付ける
const ROUTE_MAP = Object.freeze({
home: '/home',
dashboard: '/dashboard',
help: '/help'
});
function safeRedirect() {
const params = new URLSearchParams(window.location.search);
const key = params.get('to');
const path = Object.hasOwn(ROUTE_MAP, key) ? ROUTE_MAP[key] : undefined;
if (path) {
// スキームやホストを入力から指定せず、内部パスだけへ移動
window.location.assign(path);
} else {
// 許可していない値は既定のパスへ送る
window.location.replace('/home');
}
}
解説:
- 変更前: フラグメントを移動先として使うため、絶対URLやプロトコル相対URLで外部へ移動できます。攻撃者が信頼されたサイトのリンクに
#to=と外部URLを含め、ユーザーを誘導するおそれがあります。 - 変更後: ルートの許可リストに直接定義したキーだけを選び、内部パスへ対応付けます。
toStringなどの継承したプロパティは選びません。この経路では外部URLを宛先に指定できません。