💬

セキュアなiframe通信のための技術要件

に公開

なぜiframe通信のセキュリティが重要なのか

現代のウェブアプリケーションは、サードパーティ製のウィジェットやプラグイン、広告などを<iframe>を用いて統合するのが一般的です。<iframe>は外部コンテンツを安全に隔離する「サンドボックス」として機能しますが、このサンドボックスとその親ページが安全にデータをやり取りする必要が生じたとき、重大なセキュリティ上の課題が生まれます。
この問題のコアは、ブラウザのセキュリティモデル「同一オリジンポリシー(Same-Origin Policy, SOP)」にあります。このポリシーは、異なるオリジン(プロトコル、ホスト、ポートの組み合わせ)のウィンドウ同士が互いのコンテンツにアクセスすることを原則として禁止しています。
ただ、正当な理由でiframeと通信したい場合、このSOPの壁を安全に乗り越えるための仕組みが必要になります。そのために利用するのがwindow.postMessage()です。postMessageを安全に利用するための必須知識と簡単な実装を紹介します。

window.postMessage()

window.postMessage()は、オリジンが異なるウィンドウ間で安全にメッセージを交換するために設計されたAPIです。これを利用することで、SOPの制約を意図的かつ制御された形で回避できます。

メッセージの送信

メッセージを送るには、対象ウィンドウの参照に対してpostMessageメソッドを呼び出します。

targetWindow.postMessage(message, targetOrigin)
  • targetWindow: メッセージを送りたいウィンドウの参照(例:iframeElement.contentWindowやwindow.parent)。
  • message: 送信するデータ。オブジェクトや文字列など、シリアライズ可能なデータなら何でも可能です 。
  • targetOrigin: メッセージを受け取るウィンドウが持つべきオリジンを文字列で指定します。

メッセージの受信

メッセージを受け取るには、messageイベントリスナーを登録します。

window.addEventListener('message', (event) => { /*... */ });

イベントハンドラに渡されるMessageEventオブジェクトには、以下の重要なプロパティが含まれています。

  • event.data: 送信されたメッセージ本体。
  • event.origin: メッセージを送信したウィンドウのオリジン。
  • event.source: 送信元ウィンドウへの参照。返信に利用できます。

postMessageのセキュリティは、以下の2つのルールを絶対に守ることで成り立ちます。これを怠ると、深刻な脆弱性につながる可能性があります。

1. 受信時:送信元オリジン(event.origin)を必ず検証する
メッセージを受け取ったら、まずevent.originが期待する送信元のものかを確認しなければなりません。この検証を怠ると、どんな悪意のあるサイトからでもメッセージを受け入れてしまい、クロスサイトスクリプティング(XSS)などの攻撃を受ける危険があります。

// 親ウィンドウでの受信処理
const expectedOrigin = "https://trusted-plugin.example.com";

window.addEventListener('message', (event) => {
  // 1. 送信元オリジンを厳格に検証する
  if (event.origin!== expectedOrigin) {
    console.warn(`予期しないオリジンからのメッセージは破棄します: ${event.origin}`);
    return;
  }

  // オリジンが検証された後で初めてメッセージを処理する
  console.log("信頼できるメッセージを受信しました:", event.data);
});

今回は実装していませんが、オリジンを検証した後も、受け取ったデータ (event.data) が想定内の形式・内容であるかを検証することが推奨されます。これにより、信頼できるオリジンからであっても、予期せぬデータによって引き起こされる問題を未然に防ぐことができます。

2. 送信時:送信先オリジン(targetOrigin)を常に明示的に指定する
メッセージを送信する際、targetOriginに""を指定するのは絶対に避けるべきです。""はどのオリジンにでもメッセージを送ることを許可するため、もしiframeが悪意のあるサイトに遷移させられた場合、そこに機密情報を含むメッセージが漏洩してしまいます。

// 親ウィンドウからiframeへの送信処理
const iframe = document.getElementById('pluginFrame');
const targetOrigin = "https://trusted-plugin.example.com";
const message = { type: 'INIT', token: 'sensitive-auth-token' };

// 2. 送信先オリジンを厳格に指定する
iframe.contentWindow.postMessage(message, targetOrigin);

この2つのルールを徹底することが、安全なiframe通信の絶対的な前提条件です。

親から子への情報の送信

親ウインドウからiframeへの通信については、同様にpostMessageを利用し、送信元の検証などを行うことで比較的安全な通信を行うことができます。その際には、送信先について許可されたoriginなど間違いがないかを確認すると同時に、必要最低限に絞った情報を送信することを心掛ける必要があります。

実際の活用

実際の活用段階だとサードパーティ製の情報をバックエンドに送信することも出てくるかと思います。その際はプラグインから受け取った情報をフロントエンド側で検証し、API経由でバックエンドに送信することになります。その際には送られてきた情報がどこからやってきたものかなど、情報を記録しておく必要もあり、実際の活用場面だと、場合によってはさらに検証や規制が必要になると考えられます。
例えば、サードパーティの決済プラグインから受け取った決済IDをバックエンドに送信するケースを考えます。この際、フロントエンドは受け取ったIDが本当にそのプラグインから送られてきたものか(オリジン検証)を確認するだけでなく、「このIDはユーザーAの決済処理に関するものである」といったコンテキスト情報と紐づけてバックエンドに送信する必要があります。バックエンド側でも、そのIDが正当なものであるかを再度検証する多層防御の考え方が重要です。

まとめ

iframe通信を安全に行うための方法を紹介をしました。iframeをそもそも使わないというのがセキュリティ的にはより良いのは間違いないですが…場合によっては活用が必要になる場面もあるかと思います。
必要になった場合には通信の送信先や送信元の検証を必ず行い、必要最低限の情報をやり取りすることでセキュリティリスクを必要最小限におさえることを意識が重要です。iframeを利用しなければならない場合には参考にしていただければと思います。

コラボスタイル Developers

Discussion