説明
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の実装は省略しており、サーバー側で注文へのアクセス権限と決済条件を検証する必要があります。