postMessageの対象オリジンにワイルドカードを使用

postMessageのワイルドカード対象オリジンによるデータ漏えい

説明

window.postMessageのtargetOriginに'*'を指定すると、対象のウィンドウやiframeのオリジンに関係なく、そのウィンドウへメッセージを送ります。すべてのウィンドウへ一斉送信するわけではありませんが、対象が攻撃者のオリジンへ移動できる場合はトークンや個人情報が漏えいするおそれがあります。攻撃の可否は、ウィンドウへの参照、移動できる条件、送信するデータによって異なります。

想定される影響

  • 悪意のあるオリジンにトークン、セッション情報、個人情報を受信される可能性があります。
  • 漏えいした認証トークンやOAuthコードを使い、なりすましやAPI権限の悪用を行われるおそれがあります。
  • 信頼するウィンドウ向けの命令やデータが第三者へ渡り、決済や設定変更が誤って処理される可能性があります。

対処方法

  • 機密情報を含むメッセージは、スキーム・ホスト・ポートを含む正確なオリジンにだけ送ってください。https://example.comなど、信頼する設定値を使ってください。
  • 複数の送信先が必要なら許可リストから選び、一致しない場合は送信しないでください。操作可能なiframe.srcからオリジンを取得するだけでは不十分です。
  • 受信側ではevent.origin、信頼するウィンドウを示すevent.source、メッセージの型と各フィールドを検証してください。
  • 機密情報の送信を最小限にし、必要なワンタイムトークンの用途、受信者、有効期限を制限してください。
  • iframeの送信先を信頼する値に固定し、必要なsandbox制限を適用してください。openerとの通信が不要な新規ウィンドウにはnoopenerを適用できます。

例

変更前

javascript
// sender(親ページ)
const iframe = document.getElementById("payFrame");
// ローカルの認証トークンを送信(機密情報)
const token = localStorage.getItem("auth_jwt");

// '*'では対象のオリジンに関係なくメッセージが届く
iframe.contentWindow.postMessage({ type: "AUTH", token }, "*");

変更後

javascript
// sender(親ページ)
const iframe = document.getElementById("payFrame");
// 信頼する設定で対象オリジンを固定
const TARGET_ORIGIN = "https://pay.example.com";

// 機密情報を送らないか、ワンタイムコードなどで最小限にする
const message = { type: "REQUEST_PAYMENT", orderId: "ORD-2025-0912-001" };
iframe.contentWindow.postMessage(message, TARGET_ORIGIN);

// receiver(子iframeのページ)
window.addEventListener("message", (event) => {
  // 想定する親のオリジンだけを許可
  if (event.origin !== "https://app.example.com") return;
  if (event.source !== window.parent) return;
  // メッセージの形式と型を検証
  if (!event.data || event.data.type !== "REQUEST_PAYMENT") return;
  if (typeof event.data.orderId !== "string") return;

  // 注文のアクセス権限と決済条件はサーバーで別途検証
  processPayment(event.data.orderId);
});

変更前は、対象iframeが攻撃者のオリジンへ移動すると認証トークンを受信できます。変更後は送信先を固定し、受信側でオリジン、親ウィンドウ、メッセージのフィールドを確認します。senderとreceiverは別々のページで動くコードです。processPaymentの実装は省略しており、サーバー側で注文へのアクセス権限と決済条件を検証する必要があります。

参考資料