Reactでaタグの中にtarget='_blank'を書いたらESLintに怒られた話


経緯

適当に別タブを開くリンクをjsxで書いたら eslint-plugin-reactjsx-no-target-blank で怒られいました。
ほんとに知らなかったし、気軽にできちゃうからやばいな。っと思ったのでメモしておきます。

import React from 'react';
import ReactDOM from 'react-dom';

class Index extends React.Component {
  render() {
    return (
      <a
        href="http://example.com"
        target="_blank"
      >
        アンカーリンク
      </a>
    );
  }
}

ReactDOM.render(
  <Index />,
  document.getElementById('root')
);

エラーを見る

セキュリティーリスクがあるだと。。。

原文
Using target="_blank" without rel="noopener noreferrer" is a security risk: see https://mathiasbynens.github.io/rel-noopener/
意訳
rel="noopener noreferrer"つけないでtarget="_blank" 使うとセキュリティーリスクあるからやめろ!
(https://mathiasbynens.github.io/rel-noopener/)を参照しなさいっ!

攻撃方法を調べた

ざっくりいうと

  • 悪さするリンクをpwを盗みたいサイトに貼る
  • ユーザーがリンクを踏む
  • 別のタブが開き今まで見てたタブが裏に隠れる
  • 裏に隠れたタブに戻るとログイン画面になっている(実際はログインページではなくフィッシングサイトにリダイレクトされている)
  • ユーザーがid/pwを入力してログインしたらもとのページにリダイレクトする

これ最近のサイト裏でセッション切れたかなにかでログインページに飛ばされてることあったりして普通に入力してしまいませんか???

攻撃する方が準備するのは

  • ログインページ(ほぼトレースで作れるから数秒で作れちゃうはず)
  • id/pwを送ってもらうサーバー(適当でいいなら数時間かかる?ぐらい)
  • リンク先のページ(window.opener.location = "http://example.com";が実行できればどんなページでも良さそう)
  • 狙ってるサイトにリンクを配置する

めっちゃかんたん。あとは勘違いしてユーザーにログインしてもらうだけ。かなりまずい気がする。

対策

  • サイトにリンクを貼らせない(ほぼ無理)
  • ユーザーにurl変わってないか判断してもらう(かなり無理)
  • rel="noopener noreferrer"を指定する

rel="noopener noreferrer"とは

noopenerとは

そもそもwindow.opener.locationを書き換えられなくする!
ただし対応ブラウザが少なめ。

参考サイト
https://html.spec.whatwg.org/multipage/semantics.html#link-type-noopener

抜粋
its window.opener property will be null.
意訳
window.opener プロパティnullにするよ

noreferrerとは

そもそもリンクたどるときリファラー情報見れなくする!
古いブラウザでも対応してる。
別の所で情報見れなくてこまることもありそう。
noreferrerを当てるだけでESlintのエラーに対応できそうですが、 noreferrer noopener としっかり書かないとだめみたいです。

参考サイト
https://html.spec.whatwg.org/multipage/semantics.html#link-type-noreferrer

抜粋
It indicates that no referrer information is to be leaked when following the link.
意訳

リンクをたどるときにリファラー情報漏らさないよ
抜粋
<a href="..." rel="noreferrer" target="_blank"> has the same behavior as <a href="..." rel="noreferrer noopener" target="_blank">.
意訳
noreferrerとnoreferrer noopenerは同じ

セキュリティーリスクを回避してESlintのエラー修正

import React from 'react';
import ReactDOM from 'react-dom';

class Index extends React.Component {
  render() {
    return (
      <a
        href="http://example.com"
        target="_blank"
+       rel="noreferrer noopener"
      >
        アンカーリンク
      </a>
    );
  }
}

ReactDOM.render(
  <Index />,
  document.getElementById('root')
);

まとめ

普段そこまでセキュリティー意識してなくてすいません。って気持ちになりました。
ESlintさまさまです。よくわからないエラーが出てたらちゃんと分かるまで調べましょうね。

僕から以上。