t28.dev

もうなんとなくで target="_blank" と一緒に noreferrer/noopener を書かない

2022/3/21
Tech

<a> で 新しいタブを開く (target="_blank" を付ける) ときは、noreferrernoopener を付けようね〜」 という話はよく聞きます1

ところが「なんで?」と問われると「セキュリティ面で…」と曖昧だったので、理由・対策をしっかり理解しておきます。

セキュリティとプライバシー

MDN では以下の通り言及されています。

<a> 要素は、ユーザーのセキュリティやプライバシーに影響を及ぼす可能性があります。

target="_blank"rel="noreferrer"rel="noopener" なしで使用すると、ウェブサイトが window.opener API 搾取攻撃を受けやすくなります

rel="noreferrer"

Referer リクエストヘッダーにはリクエスト元のページの URL が含まれていて、MDN のReferer ヘッダーのプライバシーとセキュリティの考慮事項 で問題点が言及されています。

  • Referer ヘッダー内のアクセス元の URL から情報の追跡や盗用されたり、機密情報が漏れる可能性がある2
  • 例えばパスワードリセットページでセキュリティを侵害する恐れがある例
    • ソーシャルメディアへのリンクをクリックする
    • サードパーティ側でホストされている画像を読み込む

このようなリスクを低減するためにリファラーの送信を制限する方法として noreferrer があるという感じですね。

rel="noopener"

Window オブジェクトには opener というプロパティがあり、これによってウィンドウを開いたウィンドウへの参照を取得することが出来ます。

例えばあるサイト A から別のサイト B を新しいタブ/ウィンドウで開いた場合に、サイト B 内の JavaScript で opener 経由で サイト A の location にアクセスすることで、サイト A の URL を変更する事ができます。

// サイト B 内の JavaScript
window.opener.location = "好きなURL"; // サイトAを別のページに遷移させられる

例えばサイト A の遷移先がサイト A そっくりなフィッシングサイトだったりすると…😭

信頼できないリンクを開くときに上記のように遷移先で遷移元の情報を取得したり変更を出来ないようするために、noopener があるという感じですね。

暗黙的な振る舞い

target="_blank" だけを指定した <a> はどう振る舞うの?というと、モダンな ブラウザでは noopener を設定した振る舞いになります。

ref: MDN - <a>: アンカー要素 - ブラウザーの互換性

また、noreferrer を指定すると、noopener を設定しているかのように動作します。

ref: MDN - リンク種別: noreferrer

取りうる属性値の設定パターン

前述の情報を元に目的別の設定をまとめると以下のとおりです。

  1. referrer を送信しない、かつ opener を参照させないために (★)
    • noreferrer のみを設定する
    • noreferrer noopener 両方を設定する
  2. referrer を送信する、かつ opener を参照させないために
    • noopener を設定する

(★)の設定はどちらがベストか検討します。

ESLint (のプラグイン) が求める属性値

ESLint のルールを見てみると、React と Vue のプラグインで方針が割れています。

eslint-plugin-react では元々両方の設定を求めていましたが、noreferrer のみの設定に issues での議論の結果、実装が変更されたようです。

ref: issues(#2022), PR(#2043)

議論のポイントは noreferrer のみ設定と noreferrer noopener 両方設定でブラウザの振る舞いに違いがあるかです。そして検証の結果、違いがないので noreferrer のみでよいとなっています3

ref: issues(#2022#issuecomment-526293976)

で、どうしよっか

私個人の判断としては eslint-plugin-react の方針にならって下記のようにしていきます。

  1. referrer を送信しない、かつ opener を参照させないために、noreferrer のみを設定する
  2. referrer を送信する、かつ opener を参照させないために、noopener を設定する

おまけ

振る舞いの検証

遷移先で以下のような遷移元の操作が出来るかを確認してみます。

// 遷移先で開発者ツールを開いて...
// このブログページの window オブジェクトが取れる
window.opener;
// ページを書き換えちゃう
window.opener.location = "https://lovelive-anime.jp";

export const url = ”https://t28.dev“;

リンク or ボタン新しい Chrome の場合の期待値
rel なしのリンクopener が null
rel=“opener” のリンクページを書き換えられる
rel=“noopener” のリンクopener が null
rel=“noreferrer” のリンクopener が null

ちなみに、referrer な rel 属性はない。

Footnotes

  1. 本当に”よく”聞くことかは分からない…。Web アプリケーションの開発の”基本” ってどの範囲なのか曖昧なのが難しい…。

  2. 一方、Referer には分析・ログ・キャッシュの最適化などの用途もあるため、要件次第でもある。

  3. Firefox でバグが発生している一部のバージョンを除く