結論(postMessageの送受信元を限定)
postMessageの送受信元を限定するとは、送るときと受け取るときの両方で相手のオリジンを確かめることです。window.postMessage() でデータを送るときは宛先オリジン(targetOrigin)を "*" ではなく具体的に指定し、message イベントを受け取るときは event.origin を許可リストと完全一致で比較して、想定外の送信元や構造の不正なメッセージを破棄します。これを怠ると、データの横取りや、受信データ経由のDOM型XSSにつながります。
この記事のポイント
- 送信側は targetOrigin に "*" を使わず、相手のオリジンを具体的に書きます。
- 受信側は event.origin を完全一致で比較します。indexOf や endsWith の部分一致は回避されます。
- event.source と event.data の型・構造も検証し、受信値を innerHTML や eval に渡しません。
- 他サイトからのメッセージを受け取る必要がなければ、message リスナー自体を登録しないのが最も確実です。
- セキュリティAIチェッカーは、ワイルドカード送信またはorigin検証のない受信処理の兆候があれば△と判定します。
| 診断項目名 | postMessageの送受信元を限定 |
|---|---|
| 項目ID | html.postmessage-origin-signal |
| 分類 | HTML・JavaScript・外部資産 |
| 重大度・配点の重み | 重要(high) 重み 3 |
| 関連する標準・ガイドライン | OWASP ASVS 5.0.0 V3.5.5(L2: 信頼できないオリジンからのメッセージや構文不正なメッセージを破棄)/OWASP WSTG-CLNT-11「Web Messaging」/CWE-346(オリジン検証の誤り) |
postMessageの送受信元を限定するとは
postMessageの送受信元を限定するとは、window.postMessage() でデータを送るときに宛先オリジンを具体的に指定し、受け取るときに送信元オリジン(event.origin)とメッセージの構造を検証して、想定外の相手とのやり取りを拒否することです。
window.postMessage() は、異なるオリジンのウィンドウやiframeの間で安全にデータをやり取りするためのブラウザAPIです。自治体サイトでは、埋め込み地図、チャットボット、予約システム、アンケート、動画プレーヤー、翻訳ウィジェットなどのiframeと親ページの間で使われます。
安全に使うには、送信時に宛先オリジンを具体的に指定し、受信時に送信元オリジン(event.origin)とメッセージの構造を検証する必要があります。セキュリティAIチェッカーのこの項目は、トップページのHTMLに含まれるインラインスクリプトから、ワイルドカード送信や、オリジン検証のない受信処理の兆候を静的に検出します。
postMessageの送信側と受信側で何を限定すべきか
送信側:targetOriginを固定する
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow.postMessage(message, { targetOrigin, transfer });
targetOrigin は、受け取る側のウィンドウが現在どのオリジンの文書を表示しているべきかを指定します。"*" を指定すると、相手ウィンドウが別サイトへ遷移していても送信されます。MDNは「宛先文書の場所が分かっている場合は * ではなく具体的な targetOrigin を常に指定すること」とし、悪意あるサイトがウィンドウの遷移先を知らないうちに変更し、送信データを横取りできると説明しています。
受信側:event.originと構文を検証する
message イベントは、どのオリジンのページからでも送られてきます。受信側は次の点を確認する必要があります。
event.originが許可したオリジンと完全一致するか(indexOfやendsWithによる部分一致はhttps://city.example.lg.jp.evil.exampleのような回避を許す)- 必要に応じて
event.sourceが想定したiframeのcontentWindowか event.dataの型・構造が想定どおりか(ASVS V3.5.5は構文不正なメッセージの破棄も要求)- 受け取った値を
innerHTMLやevalに渡さない(OWASP HTML5 Security Cheat SheetとWSTG-CLNT-11が、DOM型XSSの典型として注意を促しています)
MDNはさらに、他サイトからのメッセージを受け取る必要がないなら message リスナーを登録しないことが最も確実な対策だとしています。
postMessageについてよくある誤解
- 「iframeの中身は自分たちが用意したものだから
*でよい」:iframeやポップアップのウィンドウは別サイトへ遷移させられる可能性があり、そのときに送ったデータが漏れます。 - 「
event.originを参照していれば安全」:ログ出力のために参照しているだけで比較していない、部分一致で比較している、という実装もあります。 - 「Workerの
onmessageも同じ問題」:Dedicated Workerとのメッセージは同一オリジンのスクリプト間通信であり、ウィンドウ間メッセージとはリスクの性質が異なります。
postMessageの送受信元を限定の診断基準(セキュリティAIチェッカー)
取得対象
- 入力URLのリダイレクト後の最終URLのHTML本文(最大2MB)を文字列として解析します。
- 外部JavaScriptファイル(ベンダーのウィジェット本体など)は取得しないため、その中の実装は判定に含まれません。静的な文字列パターンによる「兆候」の判定であり、実際の動作を確認したものではありません。
判定に使う4つの兆候
| 変数 | 検出条件 |
|---|---|
| 利用兆候 | postMessage(、addEventListener("message" / ('message'、グローバルの onmessage =(素の onmessage =、または window./self./globalThis.onmessage =)のいずれか |
| ワイルドカード送信 | postMessage( の後300文字以内に , "*"(または '*')が続き、その直後が ) か ,(transfer引数付き)である/またはオプション形式の targetOrigin: "*" |
| 受信処理 | addEventListener("message" またはグローバルの onmessage =(同上)。ws.onmessage =・worker.onmessage = のような他オブジェクトのプロパティへの代入は数えない |
| オリジン検証の兆候 | event.origin・evt.origin・ev.origin・e.origin・msg.origin・message.origin・messageEvent.origin の参照、または origin === / !== / == / != に続く文字列リテラルとの比較 |
判定条件
| 判定 | 条件 | 得点への影響 |
|---|---|---|
| 対象外(na) | 利用兆候がない | 採点の分母から除外 |
| △ 要確認(warn) | ワイルドカード送信がある、または受信処理があるのにオリジン検証の兆候がない | 重み3×0.45 |
| ○ 問題なし(pass) | 利用兆候があり、上記のどちらにも該当しない | 重み3を満点で加算 |
- 静的解析では、実際に機密データを送っているか、比較が完全一致かまでは判断できないため、問題の兆候があっても×(fail)ではなく△(要確認)とします。
- 根拠欄には、ワイルドカード送信(「postMessageの送信先にワイルドカードを使う兆候があります。」)か、受信時の検証欠如(「message受信処理にevent.origin検証の静的兆候がありません。」)かを区別して表示します。
- 重大度はhigh、重みは3です。
自治体サイトでの判定例
| 取得HTMLの記述 | 判定 |
|---|---|
予約システムのiframeの高さ調整で parent.postMessage({h: document.body.scrollHeight}, "*") | △(高さ情報だけなら影響は小さいが、宛先固定を推奨) |
window.addEventListener("message", function (e) { if (e.origin !== "https://map.example-vendor.jp") return; ... }) | ○ |
window.addEventListener("message", function (ev) { document.getElementById("box").innerHTML = ev.data; }) | △(検証なし、かつDOM挿入) |
| postMessageの記述がHTMLにない | 対象外 |
検出の限界
if (e.origin.indexOf("example.lg.jp") > -1)のような部分一致の比較も、e.originを参照しているため「検証の兆候あり」と扱われます。部分一致は回避可能な不十分な検証であり、○でもコードレビューで比較方法を確認してください。- 受信処理を外部JSファイルに置いている場合は検出されません。
ws.onmessage = ...・worker.onmessage = ...のようなプロパティ代入は、WebSocketやWeb Workerの受信処理とみなして数えません。一方addEventListener("message"は登録先がウィンドウかどうかを区別しないため、WebSocket、Web Worker、EventSource(Server-Sent Events)、BroadcastChannelに対する登録(例:ws.addEventListener("message", ...))も「受信処理」と数え、ウィンドウ間メッセージを使っていないページでも、オリジン検証の兆候がなければ△になります。Worker内スクリプトの素のonmessage =も同様です。この場合は根拠欄が「message受信処理にevent.origin検証の静的兆候がありません」となるので、該当コードがウィンドウのmessageイベントかどうかを目視で確認してください。const { origin, data } = event;のような分割代入で取り出し、文字列リテラル以外(定数や配列)と比較している場合は、検証していても兆候として認識されず△になることがあります。
postMessageの送受信元を限定しないとどんなリスクがあるか
- 情報漏えい:ワイルドカード送信で、入力途中のフォーム内容、利用者識別子、トークンなどを送っている場合、相手ウィンドウを攻撃者のページへ遷移させることでデータを受け取られます。
- DOM型XSS:オリジン未検証の受信処理が
event.dataをHTMLとして挿入したり、URLとして遷移に使ったりすると、攻撃者のページからメッセージを送り込むだけで、自治体サイト上でスクリプトを実行される可能性があります。 - 偽の操作指示:「ログアウトせよ」「この画面へ遷移せよ」といった制御メッセージを受け付ける実装では、外部から画面遷移や状態変更を引き起こされます。
- 埋め込み元のなりすまし:自治体サイトのウィジェットが他サイトに埋め込まれる場合、受信側で送信元を確認しないと、任意のサイトから操作されます。
innerHTML や eval など危険なAPIの使用そのものは「危険なJavaScript APIの兆候」でも確認しています。
postMessageの送受信元を限定する実装方法・対策
1. 送信側:宛先を固定する
const MAP_ORIGIN = "https://map.example-vendor.jp";
const frame = document.getElementById("map-frame");
frame.contentWindow.postMessage({ type: "setCenter", lat: 35.0, lng: 135.0 }, MAP_ORIGIN);
// iframe側から親へ送る場合も親のオリジンを固定
parent.postMessage({ type: "resize", height: document.body.scrollHeight }, "https://www.city.example.lg.jp");
2. 受信側:完全一致の許可リストと構造検証
const ALLOWED = new Set(["https://map.example-vendor.jp", "https://reserve.example-vendor.jp"]);
window.addEventListener("message", (event) => {
if (!ALLOWED.has(event.origin)) return; // 完全一致
const frame = document.getElementById("map-frame");
if (event.source !== frame.contentWindow) return; // 送信元ウィンドウも確認
const data = event.data;
if (typeof data !== "object" || data === null || data.type !== "resize") return;
const h = Number(data.height);
if (!Number.isFinite(h) || h < 0 || h > 10000) return; // 値の範囲検証
frame.style.height = `${h}px`; // innerHTMLは使わない
});
文字列を表示する必要がある場合は textContent を使います。
3. 不要なリスナーを置かない
過去のキャンペーンページの名残など、使っていない message リスナーは削除します。CMSのテンプレートに共通で入っている場合は、必要なページだけに限定します。
4. ベンダー製ウィジェットの確認
外部JSの中身はこの診断では見えません。チャットボットや地図などの導入時には、ベンダーに「送信先オリジンの固定」「受信時のorigin完全一致検証」「受信データのHTML挿入の有無」を仕様書や回答書で確認してください。
5. 併用する防御(サイトを壊さない範囲で)
Content-Security-Policy: frame-src https://map.example-vendor.jp https://reserve.example-vendor.jp; frame-ancestors 'self'
frame-src で埋め込めるiframeの配信元を限定し、frame-ancestors で自サイトを埋め込めるサイトを限定します。外部から埋め込まれることを前提とするページでは frame-ancestors に埋め込み元を追加してください。既存の埋め込みを洗い出したうえで、Report-Onlyから始めます。埋め込む側のiframeの制限は「外部iframeのsandbox」、埋め込まれる側の制限は「クリックジャッキング対策」で確認します。
6. 確認方法
curl -s https://www.city.example.lg.jp/ | grep -n -E 'postMessage|addEventListener\(\s*["'"'"']message|onmessage'
ブラウザの開発者ツールで、Sourcesパネルのイベントリスナーのブレークポイントにある「message」を有効にし、受信処理がoriginを比較しているかを実際に確認します。異なるオリジンのテストページから postMessage を送り、無視されることを確かめるとより確実です。修正後はセキュリティAIチェッカーで再診断し、根拠欄の表示が変わったことを確認してください。
よくある質問(FAQ)
postMessageのtargetOriginに "*" を指定すると何が危険ですか?
相手ウィンドウが別サイトへ遷移していてもメッセージが送信されます。MDNは、悪意あるサイトがウィンドウの遷移先を知らないうちに変更し、送信データを横取りできると説明しており、宛先が分かっている場合は具体的なtargetOriginを常に指定するよう求めています。
event.origin はどのように比較すればよいですか?
許可するオリジンをSetなどに列挙し、完全一致で比較します。indexOf や endsWith による部分一致は、https://city.example.lg.jp.evil.example のようなオリジンで回避されます。
iframeの中身が自分たちのページなら "*" でもよいですか?
推奨しません。iframeやポップアップのウィンドウは別サイトへ遷移させられる可能性があり、そのときに送ったデータが漏れます。iframeの高さ調整のような送信でも、宛先オリジンの固定を推奨します。
Web WorkerのonmessageやWebSocketのmessageイベントも同じ問題ですか?
Dedicated Workerとのメッセージは同一オリジンのスクリプト間通信で、ウィンドウ間メッセージとはリスクの性質が異なります。ただし診断では addEventListener("message" の登録先を区別しないため、ws.addEventListener("message", …) なども受信処理と数え、検証の兆候がなければ△になります。
postMessageの送受信元の診断で○なら安全ですか?
○は静的な兆候の判定です。e.origin.indexOf(…) のような部分一致でも「検証の兆候あり」と扱われ、外部JSファイル内の実装も確認していません。○でもコードレビューで比較方法を確認してください。
ベンダー製のチャットボットや地図はどう確認すればよいですか?
外部JSの中身はこの診断では見えません。導入時にベンダーへ「送信先オリジンの固定」「受信時のorigin完全一致検証」「受信データのHTML挿入の有無」を仕様書や回答書で確認してください。
この項目を含む127項目を、URLを入れるだけで無料診断します。