オープンリダイレクト(open redirect・CWE-601)は、あなたのサイトを踏み台に、利用者を攻撃者のサイトへ送り込ませる欠陥です。「ログインしたら元のページへ戻す」「このボタンを押したら指定先へ移動する」。こうした処理が、戻り先のURLをパラメータから受け取ってそのまま遷移していると、攻撃者は正規ドメインのリンクを配りながら、実際の着地点を自分のサイトにできます。利用者から見れば「信頼するサイトのURLをクリックしたのに、見分けのつかない偽サイトに着く」ことになり、フィッシングやOAuthのトークン窃取に悪用されます。
本記事は連載「事故から逆算するWebセキュリティ」の第27回です。ログイン後の戻り先やアクション後のリダイレクトを書く開発者に向けて、何が危険で、どう防ぐかを整理します。セキュリティ全体の地図は「サプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説」にまとめています。
URL vs PATH根っこは『行き先を入力に決めさせるか』
入力のURLへそのまま転送は危険/相対パスか許可リストなら安全
危ない:入力URLへ転送
- next= や redirect= の値へそのまま遷移
- //evil.com・@evil.com で別ドメインへ
- 自サイト前方一致のチェックをすり抜け
- フィッシング・OAuthトークン窃取に悪用
安全:行き先を固定
- 遷移先は相対パスだけ受け付ける
- 外部が要るならホスト許可リストで検証
- ID→行き先のマップで入力にURLを持たせない
- OAuth の redirect_uri は完全一致で登録
30秒でわかる
- ・何が起きるか:遷移先を入力から取る処理で、正規サイトのリンクから攻撃者サイトへ着地させられる(CWE-601)。フィッシング・トークン窃取へ
- ・どこに潜むか:ログイン後の
?next=/?redirect=、OAuth のredirect_uri、ログアウトやアクション後の戻り先 - ・なぜ前方一致では不十分か:
//evil.com・@evil.com・末尾ドメイン偽装・エンコードで素朴な文字列一致はすり抜ける - ・効く対策:行き先を入力に決めさせず、相対パスだけ許すかホスト許可リストで完全一致照合する
同じ「行き先を外へ向ける」でも、サーバー自身にネットワークを叩かせるのはSSRF(サーバーに内側を叩かせる攻撃)で、こちらはサーバー側の通信が標的です。オープンリダイレクトは利用者のブラウザの遷移先を乗っ取る、クライアント側の誘導である点が違います。偽サイトに着いた先で入力を盗む手口はXSSと出力時のエスケープ、CSPとも地続きです。
オープンリダイレクトはどこで起きるか
影響を受けるのは、遷移先(戻り先)をユーザー入力から受け取って転送する処理です。次のような場所が候補になります。
WHERE IT HIDES『遷移先をパラメータで受ける』処理が候補
ログイン後・OAuth・ログアウトの戻り先に潜む
-
1.ログイン後の戻り先
?next= / ?return_to= / ?redirect= / ?url= に、ログイン後の遷移先URLを入れて渡す
-
2.OAuth / OIDC の redirect_uri
認可後に戻すURL。緩い一致だと攻撃者のURLへ認可コード・トークンが飛ぶ
-
3.ログアウト・アクション後
処理後に『元のページへ戻す』リダイレクトで、戻り先を入力から取る
-
4.短縮・中継・計測リンク
?target= などで宛先を受けて転送する中継エンドポイント
・ログイン後の戻り先:?next= / ?return_to= / ?redirect= / ?url= に、認証後の遷移先URLを入れて渡す
・OAuth / OIDC の redirect_uri:認可後に戻すURL。緩い一致だと攻撃者のURLへ認可コードやトークンが飛ぶ
・ログアウト・アクション後:処理後に「元のページへ戻す」リダイレクトで、戻り先を入力から取る
・短縮・中継・計測リンク:?target= などで宛先を受けて転送する中継エンドポイント
影響範囲は「どこへ飛ばせるか」で決まります。ただのフィッシング誘導にとどまらず、OAuthの戻り先を攻撃者のURLにできれば認可コードやトークンの窃取に直結し、アカウント乗っ取りまで届きます。正規ドメインのリンクなので、利用者もメールのフィルタも「安全なリンク」と判断しやすく、気づかれにくいのも厄介です。
オープンリダイレクトは単体では「別サイトに飛ぶだけ」に見えるため、脆弱性診断でも低リスク(情報的)扱いで後回しにされがちです。だが実際の事故では、これが起点になって被害が拡大します。たとえば正規サイトのログインURLに戻り先を仕込んで配り、利用者がログイン後に瓜二つの偽ログイン画面へ着地する——という連鎖で認証情報を抜かれます。OAuthと組み合わさると、前述のとおりトークン窃取に化けます。「飛ぶだけ」で片付けず、認証やOAuthの導線上にあるリダイレクトは優先して直すのが実務的な判断です。
なお、サーバー内部で別のハンドラへ処理を渡す「フォワード(forward)」も同じ根を持ちます。遷移先の指定をユーザー入力から取ると、本来アクセスさせたくない内部のパスへ処理を回され、認可を飛び越える事故(オープンフォワード)になり得ます。ブラウザを外部へ飛ばすリダイレクトだけでなく、内部フォワードの遷移先も入力に委ねないという点で、守り方は共通です。
なぜ前方一致や記号チェックでは防げないのか
いちばんありがちな守りが、「遷移先が自サイトのURLで始まるか」を文字列で確かめる前方一致です。これは次の理由で破られます。
・スキーム相対URL:// で始まる値は、ブラウザが「別ホストへの絶対URL」として解釈する。自サイト名で始まらないのに外部へ飛ぶ
・userinfo の悪用:https://自サイト@外部 の形は、@ の前がユーザー情報として扱われ、実際の接続先は @ の後ろ
・末尾ドメイン偽装:https://自サイト.攻撃者ドメイン は、素朴な「自サイトを含むか」判定を通り抜ける
・区切り・エンコード:バックスラッシュや二重エンコードを混ぜると、判定とブラウザの解釈がずれる
要は、守るべきは「文字列の見た目」ではなく、その値が最終的にどのホストへ遷移するかです。URLを正しく構造として解釈しないまま前方一致で見ると、表現の差で必ずすり抜けられます。攻撃者が送れる入力と、守りが無いときに起こることを整理します。
| 攻撃者が送れる入力 | 守りが無いと起こること |
|---|---|
// で始まる値 |
別ホストへの絶対URLとして遷移する |
userinfo(@)を含む値 |
@ の後ろのホストへ接続する |
| 自サイト名を接頭辞に足した別ドメイン | 前方一致・部分一致を通り抜ける |
| OAuth の緩い redirect_uri | 認可コード・トークンが攻撃者のURLへ飛ぶ |
オープンリダイレクト対策——相対パスか許可リストで行き先を固定する
効くのは、記号を弾くことではなく、行き先を入力に決めさせないことです。OWASP の Unvalidated Redirects and Forwards Cheat Sheet も、可能ならリダイレクトにユーザー入力を使わず、使うなら許可リストで検証することを勧めています。順序はこうです。
SAFE DESIGN設計の順序
URLを受けない → 相対パス/ID → 外部は許可リスト → 偽装パターンに注意
-
1
行き先を入力に決めさせない
遷移先は相対パスだけ、またはID→行き先のサーバー側マップで引く。完全なURLをパラメータで受けない
-
2
相対パスとして強制する
先頭が単一スラッシュで始まる自サイト内パスのみ許可。スキームやホストを含む値は弾く
-
3
外部が必要ならホスト許可リスト
URLを正しくパースし、host を許可リストと完全一致で照合。前方一致・部分一致で判定しない
-
4
偽装パターンを潰す
スキーム相対(//)・userinfo(@)・末尾ドメイン偽装・バックスラッシュ・エンコードに注意。OAuth は redirect_uri 完全一致登録
ポイントは、完全なURLをパラメータで受けないことです。遷移先が自サイト内で足りるなら、受け付けるのは相対パスだけにします。具体的には「単一のスラッシュで始まる自サイト内パス」だけを許し、スキームやホストを含む値、// で始まる値は弾きます。外部への遷移が本当に必要なときは、URLを言語のパーサで構造として解釈し、host を信頼ドメインの許可リストと完全一致で照合し、スキームも http/https に限ります。守り方ごとに、何を防ぎ、何を防がないかを並べます。
| 守り方 | 何を防ぐか | 何を防がないか |
|---|---|---|
| 前方一致(自サイトで始まるか) | ごく単純な別URL | スキーム相対・userinfo・末尾ドメイン偽装 |
| 相対パスだけ許可(単一スラッシュ始まり) | 外部ホストへの遷移全般 | アプリ内の不適切な遷移先(権限確認は別) |
| URLをパースして host を許可リスト完全一致 | 許可外ドメインへの遷移 | 許可ドメイン自体が乗っ取られた場合 |
| ID→行き先のサーバー側マップ | 入力にURLを持たせない(経路ごと) | マップの管理漏れ |
いちばん固いのは、行き先の候補をサーバー側に持ち、識別子で選ばせる設計です。OAuth/OIDC の redirect_uri も同じ考えで、登録済みの値と完全一致でのみ許します。
言語・フレームワークでの要点
多くのフレームワークは「安全なリダイレクト」の手段を用意しています。要点は、生のURL文字列をそのまま Location に入れないことです。
- 相対パスへ正規化してから遷移:受け取った値をURLとしてパースし、
schemeとhostが空(=相対)であることを確認してから使う。スキームやホストが付いていたら拒否する。 - ホスト許可リスト:外部遷移が必要なら、パース結果の
hostを許可リストと完全一致で照合する。文字列のstartsWithではなく、パーサのhostを使う。 - フレームワークの保護を使う:一部のフレームワークは「自ホスト以外へのリダイレクトを既定で拒否する」オプションや、安全なリダイレクト用のヘルパを持つ。自前で判定する前に、使っているフレームワークの該当機能を確認する。
- OAuth:
redirect_uriは登録済みと完全一致。ワイルドカードや前方一致を使わない。
注意点は、パースは必ず言語・フレームワークの正規のURLパーサで行うことです。自前の文字列処理で host を取り出そうとすると、userinfo やスキーム相対の扱いを間違え、同じすり抜けを再発させます。
自分のコードを確認する
ここからは読み取りだけの確認コマンドです。いずれも判定ではなく構成の確認で、ヒットの有無だけでは安全とは言えません。該当箇所を目で追う起点として使います。. は自分のリポジトリのルートに読み替えてください。
まず、リダイレクトを発行していそうな箇所を洗い出します。
grep -rnE "(redirect|sendRedirect|Location:|res\.redirect|redirect_to|RedirectResponse|header\(['\"]Location)" . --include="*.py" --include="*.js" --include="*.ts" --include="*.php" --include="*.java" --include="*.rb" --include="*.go"
次に、その遷移先がリクエスト由来のパラメータ(next・return_to・redirect・url・target など)から来ていないかを、ヒット箇所ごとに追います。
grep -rnE "(next|return_to|redirect|redirect_uri|url|target|continue)[\"']?\s*[:=]\s*(request|req|params|query|GET|input)" . --include="*.py" --include="*.js" --include="*.ts" --include="*.php" --include="*.java" --include="*.rb" --include="*.go"
いずれも、小さなダミーのプロジェクトで陽性(入力URLへそのまま遷移する側)と陰性(相対パス強制・許可リストに直した側)の両方を走らせ、拾うべきものを拾い、無いときに何も出ないことを確かめてから載せています。ヒットした箇所が本当に危険かは、その遷移先がパラメータ由来か、相対パス強制やホスト許可リストが後ろにあるかを、コードを読んで確かめます。悪用に使える具体的なURL細工は、本記事では扱いません。
参照ソース
- OWASP Cheat Sheet Series — Unvalidated Redirects and Forwards: https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
- MITRE CWE-601 — URL Redirection to Untrusted Site (‘Open Redirect’): https://cwe.mitre.org/data/definitions/601.html
- OAuth 2.0 Security Best Current Practice(redirect_uri の完全一致): https://datatracker.ietf.org/doc/html/rfc9700





