クライアント側で検証しないURLへのリダイレクト

クライアント側で検証しないURLへのリダイレクト

説明

クライアント側のオープンリダイレクトは、クエリ、フラグメント、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を宛先に指定できません。

参考資料