【実務・中級編】aタグのtarget属性とrel属性によるセキュリティ – HTML実践ガイド

`target=”_blank”`の裏側に潜む罠:なぜ我々は`rel=”noopener noreferrer”`を書き続けるのか

フロントエンドの現場で、外部サイトへのリンクを新しいタブで開くために `target=”_blank”` を書くのは、もはや呼吸をするのと同じくらい当たり前の所作です。しかし、その「当たり前」の裏側に、あなたのアプリケーションを丸裸にする脆弱性が潜んでいることを、どれだけのエンジニアが意識できているでしょうか。

今回は、単なる「お作法」として片付けられがちな `rel=”noopener noreferrer”` の重要性について、ブラウザの内部挙動を紐解きながら、明日から胸を張ってコードレビューができるレベルまで深掘りしていきます。

—

窓を開けるということ:`window.opener` の正体

`target=”_blank”` を指定してリンクをクリックした際、ブラウザは新しいタブやウィンドウでページを開きます。このとき、実は裏側で「親」と「子」の間に密接な関係性が構築されます。

具体的には、子ページ側から `window.opener` プロパティを通じて、親ページの `window` オブジェクトにアクセスできてしまうのです。

これがなぜ脅威なのか?

もし、あなたのサイトのリンク先が悪意のある第三者のサイトだった場合を想像してください。

1. ユーザーがあなたのサイトのリンクをクリックし、別タブで悪意あるサイトに遷移する。
2. そのサイトが `window.opener.location.href = ‘https://phishing-site.com’` といったスクリプトを実行する。
3. ユーザーが元のタブに戻ったとき、そこには既にあなたのサイトではなく、巧妙に偽装されたフィッシングサイトが表示されている。

ブラウザのタブは離れていても、JavaScriptのコンテキストが繋がっている。これが「タブ・ナッピング(Tabnapping)」と呼ばれる攻撃手法の正体です。

—

防御の要:`noopener` と `noreferrer`

この脆弱性を断ち切るために登場するのが、`rel` 属性です。それぞれの役割を整理しましょう。

  • `rel=”noopener”`: これが本丸です。新しいタブを開く際に、`window.opener` を `null` に設定します。これにより、子ページから親ページへの干渉が物理的に遮断されます。
  • `rel=”noreferrer”`: これは追加の保護層です。`Referer` ヘッダーを送信しないように制御します。`noopener` だけでセキュリティ上の要件は満たせますが、プライバシー保護の観点から併記するのが現代のベストプラクティスです。

かつては一部のブラウザで `noopener` がサポートされておらず、苦労した時代もありましたが、現在は主要なモダンブラウザがすべて対応済み。書かない理由はありません。

—

実践:現場で使うべき「綺麗なコード」

ただ漫然と書くのではなく、再利用性と堅牢性を意識した実装がプロの仕事です。以下に、モダンなフロントエンド開発でそのまま使えるスニペットを提示します。



外部サイトへ移動(安全な設計)

Reactなどのコンポーネントでの抽象化

もしReactなどのライブラリを使っているなら、毎回これを書くのはヒューマンエラーの元です。以下のようにコンポーネント化してしまうのが、チームの品質を担保する「賢い」やり方です。

// ExternalLink.tsx
const ExternalLink = ({ href, children, …props }) => {
return (

{children}

);
};

—

最後に:なぜ「仕様」を知る必要があるのか

「なぜこの属性が必要なのか」を知っているエンジニアと、ただコピペしているエンジニアの間には、トラブルが起きた時の対応力に決定的な差が出ます。

最近のブラウザでは、実は `target=”_blank”` を指定すると自動的に `rel=”noopener”` が付与される挙動をとるケースも増えてきました。しかし、ブラウザの仕様変更に依存するコードは「脆いコード」です。

我々フロントエンドエンジニアの仕事は、ブラウザが何をやってくれるかを期待するのではなく、「自分たちが意図したセキュリティ水準を、コードで明示的に担保すること」にあります。

明日からの開発で、見慣れた `` タグの中に潜むこの小さな属性を見直してみてください。その一手間が、あなたのプロダクトとユーザーを守る強力な盾になります。

コメント

タイトルとURLをコピーしました