LoginSignup
36

More than 5 years have passed since last update.

セキュリティ対策のため、target="_blank"のa タグに "noopener noreferrer"のrel属性を自動で付与するjavascript文

Last updated at Posted at 2019-03-10

image.png
(”寝る人”さんのブログより、とてもわかりやすい画像を引用させていただきました)

脆弱性

target="_blank"のaタグには脆弱性があります。

遷移先から遷移元のページを不正に操作したり、オブジェクトにアクセスしたりできてしまいます。
これによって、フッシング詐欺攻撃を行う余地を生んでしまいます。

サイトで rel="noopener" を使用して外部アンカーを開く(Google Developers)

監査が重要である理由
target="_blank" を使用して任意のページから別のページにリンクしている場合、リンク元のページとリンク先のページは同じプロセスで動作します。 そのため、リンク先のページで負荷の高い JavaScript が実行されていると、リンク元のページのパフォーマンスが低下するおそれがあります。
また、target="_blank" にはセキュリティ上の脆弱性もあります。リンク先のページでは window.opener を使用して親ウィンドウのオブジェクトにアクセスしたり、window.opener.location = newURL によって親ページの URL を変更したりできます。

対策

対策としては、target="_blank"を指定しいるaタグのrel属性に"noopener noreferrer"オプションを付与すれば良いです。

まずnoopenerですが、このオプションを指定することで「別タブの遷移先から、window.openerを参照できなくなる(≒不正操作ができなくなる)」という効果があります。
そのため、基本的な対策は noopenerだけで問題ありません。

しかし、Edgeなどの一部のブラウザではnoopener自体がサポートされていないこともあります。
その場合の対策としてnoreffererを指定することで、同じような挙動を実現することが可能です。

noreffererを指定することで、「遷移先のリソースからリファラーを送らないようにブラウザに指示を出す」ことが可能です。
そのため、noopener noreferrerの両方をしていすることが望ましいわけです。

ちなみに、rel属性をはじめ各ブラウザのオプション対応については以下のWebサービスで確認をすることができます。

Can I use... Support tables for HTML5, CSS3, etc

背景

既存のWebページ内で、target="_blank"オプションを指定したaタグが複数存在していました。

Reactであれば、eslintで危険なtarget="_blank"オプションが弾かれたり、wordpressであれば自動で上記のrel属性が付与されるようになっているので、あまり気にすることはありません。

しかし、生のhtmlで記述されたLPでは上記のようにデフォルトでtarget="_blank"を警戒することはできません。
ひとつひとつのaタグにrel属性を付与したりチームの規約にするのも大変なので、javascriptで自動的に属性を付与してあげるのが一番良さそうです。

スクリプト文

// aタグの中身を分解
var aTags = [].slice.call(document.getElementsByTagName('a'));

// 未対応であるIEを除外するため、ブラウザの種別を洗う
var userAgent = window.navigator.userAgent.toLowerCase();
var isIE = (~userAgent.indexOf('msie') || ~userAgent.indexOf('trident'));

function addRels() {
  if (!isIE) { //IEは弾く
    aTags.forEach(function (el) {
      if (el.target === '_blank') {
        var rels = el.rel.split(' ');
        if (!~rels.indexOf('noopener')) {
          rels.push('noopener');
          el.setAttribute('rel', rels.join(' ').trim());
        }
        if (!~rels.indexOf('noreferrer')) {
          rels.push('noreferrer');
          el.setAttribute('rel', rels.join(' ').trim());
        }
      }
    });
  }
};
// 関数を実行
addRels();

少しだけ解説

!~rels.indexOf(文字列)では、いわゆる「ビット反転演算子」を利用しています。

indexOfは「文字列が見つかれば文字列が見つかった場所(0以上)」を、「文字列が見つからなければ-1」を返します。
この挙動を利用し、「-1をビット反転演算子にかけ、0を返す」→「if文で0が真と判定される」という文字列マッチングの仕組みを実現しています。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36