もうなんとなくで target="_blank" と一緒に noreferrer/noopener を書かない
「<a>
で 新しいタブを開く (target="_blank"
を付ける) ときは、noreferrer
や noopener
を付けようね〜」
という話はよく聞きます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
を設定しているかのように動作します。
取りうる属性値の設定パターン
前述の情報を元に目的別の設定をまとめると以下のとおりです。
- referrer を送信しない、かつ opener を参照させないために (★)
noreferrer
のみを設定するnoreferrer noopener
両方を設定する
- referrer を送信する、かつ opener を参照させないために
noopener
を設定する
(★)の設定はどちらがベストか検討します。
ESLint (のプラグイン) が求める属性値
ESLint のルールを見てみると、React と Vue のプラグインで方針が割れています。
- react/jsx-no-target-blank では、
noreferrer
の設定を求めている - vue/no-template-target-blank では、
noopener
とnoreferrer
両方の設定を求めている
eslint-plugin-react では元々両方の設定を求めていましたが、noreferrer
のみの設定に issues での議論の結果、実装が変更されたようです。
ref: issues(#2022), PR(#2043)
議論のポイントは noreferrer
のみ設定と noreferrer noopener
両方設定でブラウザの振る舞いに違いがあるかです。そして検証の結果、違いがないので noreferrer
のみでよいとなっています3。
ref: issues(#2022#issuecomment-526293976)
で、どうしよっか
私個人の判断としては eslint-plugin-react の方針にならって下記のようにしていきます。
- referrer を送信しない、かつ opener を参照させないために、
noreferrer
のみを設定する - 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 属性はない。