HTML
CSS
JavaScript
HTML5
セキュリティ

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

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が真と判定される」という文字列マッチングの仕組みを実現しています。