オープンリダイレクトとは?起きるパターンと対策法

オープンリダイレクトとは?起きるパターンと対策法

#セキュリティ#Web開発#脆弱性対策

オープンリダイレクトは、遷移先を利用者に決めさせる仕組みが悪用され、フィッシングや認証トークン奪取の踏み台にされる脆弱性です。 対策の勘所は、遷移先を外部入力のまま使わず、許可リストで縛れているかどうかです。

オープンリダイレクトとは

オープンリダイレクト:リダイレクト先のURLをユーザー入力(パラメータなど)で決める設計で、任意の外部サイトへ利用者を飛ばせてしまう脆弱性です。 遷移先の値を検証せずにリダイレクトに使うことが根本原因になります。

単体ではサーバに直接の被害を与えませんが、攻撃の踏み台として効きます。 正規サイトのドメインから始まるURLで利用者を油断させ、偽サイトへ誘導するフィッシング、OAuthのリダイレクトを乗っ取っての認証コードやトークンの奪取に至ります。 リンクのドメインが正規のものなので、利用者もメールのフィルタも警戒しにくく、フィッシングの成功率を押し上げます。

オープンリダイレクトが起きるパターン

典型的な弱点は次のとおりです。

  • ログイン後の戻り先を?redirect=?next=のパラメータで受け取り、その値へ検証なしで遷移する
  • ドメイン名の前方一致だけで判定し、example.com.attacker.comのような偽装をすり抜けられる
  • //attacker.comのようなスキーム省略のURLを、相対パスと誤認してそのまま使う
  • OAuthやSSOのredirect_uriを厳密に検証せず、攻撃者のURLへ認証結果を返してしまう
  • 短縮URLやメール内リンクの中継で、遷移先を無制限に受け付ける

共通するのは「遷移先を外部入力に委ね、それが安全な宛先かをサーバで確かめていない」という点です。

対策法

原則は「遷移先を外部入力のまま使わず、あらかじめ許可した宛先だけに限定する」ことです。

  • 遷移先を許可リストや固定のマッピングで管理し、入力は選択肢の識別子として受け取る。これが本命の対策になる
  • 外部URLを許す必要があるなら、ホスト名を厳密に検証し、部分一致やスキーム省略の偽装を弾く
  • 内部遷移は相対パスに限定し、絶対URLや別ホストへの遷移を受け付けない
  • OAuthのredirect_uriは登録済みの完全一致だけを許可する
  • 外部サイトへ飛ぶ前に、遷移先を明示する中継ページを挟む(補助策)

ドメインの前方一致など緩い判定は回避されやすく、本命は許可リストによる宛先の限定です。 リダイレクトに外部入力を使う箇所を洗い出し、許可リスト方式へ寄せるのが費用対効果の高い進め方になります。

Rails での事例

危険な実装は、パラメータをそのまま redirect_to に渡す形です。

# 危険:外部ホストへのリダイレクト拒否が有効でなければ、そのまま飛ぶ
def create
  sign_in(user)
  redirect_to params[:redirect_to]
end

Railsの外部リダイレクトに対する既定動作は、実行中のRailsのバージョンだけではなくconfig.load_defaultsの対象バージョンで決まります。 Rails 7.xと8.0では、config.load_defaults 7.0を読み込むとconfig.action_controller.raise_on_open_redirectstrueになり、別ホストへのredirect_toUnsafeRedirectErrorを投げます。 古い既定値を維持したまま更新したアプリではfalseのままの場合があるため、Rails 7以降へ更新しただけで保護が有効になったとは判断できません。 Rails 8.1では設定名がconfig.action_controller.action_on_open_redirectへ変わり、config.load_defaults 7.0以降の既定値は:raiseです。 従来のraise_on_open_redirectsは非推奨ですが、移行期間中はtrueに設定されていると:raise相当の動作が優先されます。 エラーを黙らせるためにallow_other_host: trueを付けると、この保護を呼び出し単位で外してしまいます。

# 危険:これは保護を明示的に外す宣言であり、「エラーの修正」ではない
redirect_to params[:redirect_to], allow_other_host: true

安全な形は、戻り先を内部パスに限定することです。

def create
  sign_in(user)
  redirect_to safe_return_path(params[:redirect_to])
end

private

def safe_return_path(raw)
  path = raw.to_s
  # 制御文字とバックスラッシュを拒否し、単一の "/" で始まるパスだけを許可する
  return root_path if path.match?(/[\\\u0000-\u001F\u007F]/)
  return path if path.start_with?("/") && !path.start_with?("//")
  root_path
end

//evil.example はスキーム省略の絶対URLとしてブラウザに解釈されるため、先頭が / かどうかだけの判定では不十分です。 ブラウザやルーターが区切り文字として正規化する可能性があるバックスラッシュも拒否します。

React(TypeScript)での事例

SPA 側でも同じ構造の穴が生まれます。 ログイン後の戻り先をクエリパラメータから読んで遷移する実装です。

// 危険:?next=https://evil.example にも ?next=javascript:... にも飛ぶ
const next = new URLSearchParams(location.search).get("next");
window.location.href = next ?? "/";

window.location.href への代入は任意のURLを受け付けるため、外部サイトへの誘導だけでなく javascript: スキームの実行にもつながります。 直し方はサーバ側と同じで、内部パスだけを許可してルーターに渡すことです。

import { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";

function safeNext(raw: string | null): string {
  if (!raw) return "/";

  try {
    const url = new URL(raw, window.location.origin);
    if (url.origin !== window.location.origin) return "/";
    return `${url.pathname}${url.search}${url.hash}`;
  } catch {
    return "/";
  }
}

function LoginRedirect() {
  const location = useLocation();
  const navigate = useNavigate();

  useEffect(() => {
    const next = new URLSearchParams(location.search).get("next");
    navigate(safeNext(next), { replace: true });
  }, [location.search, navigate]);

  return null;
}

new URLで先に正規化してからoriginを完全一致で比較するため、//evil.exampleだけでなく、/\evil.exampleのようにバックスラッシュを使う入力も外部URLとして拒否できます。 useNavigateはReactコンポーネント内で呼び、ログイン完了後の副作用として遷移させます。 React Routerの6.0.0以上7.18.0未満には、外部入力由来のパスをLinknavigateへ渡すと予期しない外部遷移が起きる脆弱性(CVE-2026-53669)があります。 react-routerreact-router-domなどの関連パッケージを7.18.0以降へ揃えたうえで、アプリ側でも遷移先を検証します。 外部URLへ意図的に飛ばしたい場面だけを例外として設計し、既定は内部パス限定に倒します。

まとめ

オープンリダイレクト対策の要点を整理します。

  • 根本原因は「遷移先を外部入力に委ね、安全な宛先かをサーバで確かめていないこと」であり、単体ではサーバに被害を与えなくても、フィッシングとトークン奪取の踏み台として効く
  • 内部遷移を相対パスに限り、絶対URLや別ホストへの遷移を受け付けない許可リスト方式が本命の対策になる
  • /で始まり//で始まらないという文字列判定だけではバックスラッシュを使った入力を防げないため、URLとして正規化してからオリジンを完全一致で比較するか、固定した遷移先の識別子だけを受け取る
  • Rails 7.xと8.0ではconfig.load_defaults 7.0以降でraise_on_open_redirectstrueになる。Rails 8.1では後継のaction_on_open_redirect:raiseになる。更新アプリは古い既定値を維持している場合があり、allow_other_host: trueは保護を明示的に外す
  • React Routerの6.0.0以上7.18.0未満には外部入力由来のパスから外部遷移が起きる脆弱性があるため、react-routerreact-router-domなどの関連パッケージを7.18.0以降へ揃え、遷移前の検証も残す
  • OAuthやSSOのredirect_uriは、登録済みの値との完全一致だけを許可する

フロント側でも構造は同じです。 window.location.hrefへの代入は外部サイトへの誘導だけでなくjavascript:スキームの実行にもつながるため、正規化と同一オリジン確認を通した内部パスだけをルーターへ渡してください。

関連して読める記事

SSRFと名前も構図も似ていますが、飛ばされる相手が違います。オープンリダイレクトは利用者のブラウザを外部サイトへ、SSRFはサーバ自身を内部ネットワークへ向かわせます。どちらも「宛先を外部入力で決めさせない」という同じ原則で解けます。javascript:スキームの実行に至る経路はXSSと重なり、利用者を騙して意図しない操作をさせるという意味ではCSRFとも並びます。混入したリダイレクト先がキャッシュに保存されると、以降そのURLを開いた全員が誘導されるため、被害が面に広がります。

SSRF(サーバサイドリクエストフォージェリ)とは?起きるパターンと対策法

SSRF(サーバサイドリクエストフォージェリ)の仕組みと起きるパターン、宛先ホストの許可リストとクラウドメタデータ保護(IMDSv2)を軸にした対策を整理します。Railsで URI.open にユーザー指定URLをそのまま渡してしまう危険例と、許可リスト+内部IP検証を組み込んだ安全な実装、DNSリバインディングまで含めた検証の勘所を具体的なコードで示します。

2026年7月24日

XSS(クロスサイトスクリプティング)とは?起きるパターンと対策法

XSS(クロスサイトスクリプティング)の仕組みと3つの型、混入しやすいパターン、そして出力時のエスケープを軸にした対策を整理します。React(TypeScript)の dangerouslySetInnerHTML と Rails(ERB)の raw / html_safe を題材に、危険なコードと安全な書き方を具体的なコード例で示します。

2026年7月22日

CSRF(クロスサイトリクエストフォージェリ)とは?起きるパターンと対策法

CSRF(クロスサイトリクエストフォージェリ)の仕組みと起きるパターン、CSRFトークンとSameSite Cookieを軸にした対策を整理します。Railsで検証を外してしまう skip_before_action の罠と、React(TypeScript)からのトークン送信ヘッダの組み込み方を、危険なコードと安全な書き方の具体例で示します。

2026年7月23日

Webキャッシュポイズニングとは?起きるパターンと対策法

Webキャッシュポイズニングの仕組みと起きるパターン、キャッシュキーの設計と未署名ヘッダの排除を軸にした対策を整理します。request.host(X-Forwarded-Host 由来)からURLを組み立てる危険例と、config.asset_host での固定、本番で空のままになりがちな config.hosts の設定を具体的なコードで示します。

2026年7月25日
この記事をシェア