なぜ危ないのか
攻撃者は`https://yourcompany.com/redirect?url=https://phishing-site.com`という形式のURLを作り、メール文面に「公式サイト」として貼り付ける。受信者はURLのドメイン部分(yourcompany.com)を確認して安全と判断してクリックするが、実際にはフィッシングサイトへ飛ばされる。この攻撃の入口になるのが、AI生成HTMLに残るオープンリダイレクトだ。
被害が発覚するのは多くの場合、フィッシング被害を受けたユーザーから連絡が来た後だ。その時点では自社ドメインが悪用されたURLがすでにメールやSNSで拡散しており、URLを失効させても被害を止めるのが難しい。公開前の一回の確認で防げる問題であるにもかかわらず、発覚後の対応コストは非常に大きい。
社外レビュー用に「一時的に公開しておくだけ」という意識で共有されたHTMLでも、公開期間中にクローラーや自動スキャンツールにURLが発見される可能性がある。一度発見されると、その情報が悪意のある第三者に渡ることもあるため、「短期間だから大丈夫」という判断は危険だ。
- 相手がログインなしで開ける状態か確認する
- PCとスマホで最低1回ずつ表示を確認する
- 内部情報・個人情報・不要な外部送信が残っていないか見る
- レビュー期限と修正時の差し替え方を決めておく
ソースで見る場所
HTMLのscriptブロックまたは読み込み先のJSファイルで`location`オブジェクトの代入箇所を探す。`location.href = `や`window.location = `の後ろに変数が来ている行を見つけたら、その変数がどこから来ているかを追う。URLSearchParamsやatobで取り出した値が直接代入されていれば要修正だ。
フォームのaction属性も確認対象になる。`<form action="`に続くURLが静的な文字列でなく、テンプレート変数やJavaScriptで動的に設定されている場合、フォーム送信後のリダイレクト先が外部に向く可能性がある。action属性の値を全件抽出して、外部ドメインが含まれないかを確認する。
HTTPレスポンスヘッダの`Location`を動的に生成している場合もオープンリダイレクトになるが、純粋な静的HTMLの場合は該当しない。ただしAIがNode.jsのExpressサーバーコードとHTMLを一緒に生成することがあり、その場合は`res.redirect(req.query.next)`のようなサーバー側のコードも確認対象に含める必要がある。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
安全に共有する設定
オープンリダイレクトの対策として最も確実なのは、リダイレクト先URLをコード内に固定してパラメータで受け取らない設計にすることだ。「ログイン後は/dashboardへ」のように宛先が固定できる場合は、URLパラメータを使わずそのまま遷移先をハードコードする。これが最もシンプルで安全な設計だ。
パラメータでパスを受け取る必要がある場合は、先頭が`/`で始まり`//`や`http`を含まない相対パスのみ許可するバリデーションを加える。JavaScriptで`if (redirectUrl.startsWith('/') && !redirectUrl.startsWith('//'))` という条件を入れるだけで、外部URLへの誘導を防ぐことができる。
修正済みのHTMLをレビュアーに見せる際は、ギガサイト便のメール認証機能を使うと、指定したメールアドレスを持つ人だけがアクセスできるプレビューURLを発行できる。社外の関係者に送る場合も、アクセスログが残るため不正アクセスを事後確認できる。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
再発防止ルール
AIプロンプトのテンプレートに「URLパラメータをリダイレクト先として使用しないこと。リダイレクト先はコード内に固定するか、内部パスのみ許可する許可リストで検証すること」という制約を追加する。この指示はモデルに関係なく効果があり、コード生成の品質を一定水準に保てる。
新規のHTMLファイルをリポジトリにコミットする際、PR上でレビュアーがオープンリダイレクトの有無を確認するチェックリスト項目を設ける。Descriptionテンプレートに「location.hrefへの外部URL代入がないことを確認した」というチェックボックスを追加するだけで、確認漏れを防ぐ仕組みになる。
万が一オープンリダイレクトが本番環境で発見された場合に備え、URLを即座に失効させる手順を事前に定めておく。ギガサイト便のような共有サービスであればダッシュボードからワンクリックで無効化できるが、自前のサーバーの場合はCloudflare WAFのルールで特定パスへのアクセスをブロックする方法を準備しておくと対応が速い。
- HTML内の外部script・form action・iframeを確認する
- APIキーやトークンらしき文字列がないか検索する
- 画像・CSS・JSのパスが公開後も解決できるか見る
- noindexと認証を混同せず、必要なら両方設定する
よくある質問
オープンリダイレクトはどの程度の頻度でAI生成HTMLに混入しますか?
AIにSPAや管理画面のプロトタイプを生成させた場合、ログイン後リダイレクトやタブ切り替えなどのUI要件があると混入しやすい。特に「ログイン後に元のページへ戻る」という指示を与えたときに発生頻度が高まる傾向がある。
相対パスのみ許可する場合、`//evil.com`のようなダブルスラッシュはなぜ危険なのですか?
`//evil.com`は「現在のプロトコルを引き継ぐ相対URL」として解釈され、httpsで始まるページからは`https://evil.com`へリダイレクトされる。先頭が`/`だからといって安全とは限らず、`//`のチェックが必須になる。
既存のHTMLファイルが多数あって一件ずつ確認するのが大変な場合、効率的な方法はありますか?
`grep -rn 'location.href\|location.replace\|location.assign' ./` で一括検索し、ヒットしたファイルと行番号を取得してから個別に目視確認する方法が現実的だ。ヒット件数が多い場合はSemgrepのルールで自動判定することで優先度をつけられる。