テック技報最新号へ →
セキュリティ

postMessage は送信元を必ず検証し、srcdoc の iframe には sandbox を付ける:ChatGPT の共有ページから非公開会話を取れた連鎖

隔離された HTML プレビューが送る postMessage を、数式入力ライブラリー MathLive が送信元を確かめずに受け取っていたため、ChatGPT 本体と同じオリジンで JavaScript が動き、ログイン中の利用者として非公開会話を取得できた。OpenAI は 10 月 26 日までに修正済み。

@MalwareBibleJP · 2026-11-04
#xss#postmessage#iframe#csp#web-implementation#chatgpt

攻撃の流れ(研究者 Parameter の報告)

  1. グラフと HTML Preview を含む会話を通常の共有機能で公開。被害者がログインした状態で開き、Preview を押す(自動表示ならリンクを開くだけの経路も再現)。
  2. 隔離された Preview が postMessage を送る。MathLive の仮想キーボードはメッセージの送信元オリジンや送信元ウィンドウを確認せず、設定内の HTML を本体の innerHTML に挿入した。
  3. 挿入した HTML で srcdoc の iframe を作る。sandbox 属性がないため、iframe は親と同じオリジンを引き継ぐ。
  4. CSP でインラインスクリプトは禁止されていたが、CSP が許可していた YouTube の oEmbed の JSONP 応答を <script src> で読み込み、任意コードを実行。
  5. 利用者の認証状態で会話 API を呼び、会話一覧と本文を取得・外部送信。

実装で守ること

  • postMessage の受信側: event.origin を許可リストと完全一致で比較し、必要なら event.source が想定したウィンドウかも確かめる。受け取ったデータは HTML として挿入しない(textContent を使う、または型を決めて検証)。
  • ライブラリーの受信ハンドラー: 自分で書いていなくても、依存ライブラリーが message イベントを聞いていないか確認する(DevTools の Event Listeners や rg "addEventListener\(['\"]message" で探す)。
  • srcdoc / about:blank の iframe: 信頼できない内容を表示するなら必ず sandbox を付ける。allow-scripts と allow-same-origin を同時に付けない。
  • CSP の許可リスト: JSONP を返すエンドポイント(oEmbed、古い API など)を script-src に含めると、インラインスクリプト禁止を回避される。ホスト単位の許可ではなく nonce や hash(strict-dynamic)を使う。
  • 共有・プレビュー機能: 他人が作った内容を自分のログイン状態のまま描画する画面は、別オリジン(サンドボックス用ドメイン)で表示する。

出典

X の元の投稿を開く