A Novel CSP Bypass Using data: URI
I couldn’t find an XSS payload which worked in a situation with a very restrictive CSP. This post documents the things I tried and the solution I ultimately found, with the help of some coworkers.
Standard XSS Payloads Blocked By CSP
On a recent web application assessment, I ran into a DOM-based XSS1 vulnerability which had me stumped. It was a classic DOM-based XSS: the application was returning user data in a JSON blob, and inserting it into the page using
innerHTML is used to modify the DOM,
<script> tags don’t execute2, but that’s not an issue normally. Many other tags can be used, such as the following payload using an
<img src=x onerror='alert(1)'/>
myclient.com standing in for the domain I was testing):
Content-Security-Policy: default-src 'self' data: *.myclient.com; connect-src 'self' data: *.myclient.com; font-src 'self' data: *.myclient.com fonts.gstatic.com; img-src 'self' data: *.myclient.com; script-src 'self' data: *.myclient.com; style-src 'self' data: *.myclient.com; report-uri /_csp; upgrade-insecure-requests
I entered the policy into Google’s CSP Evaluator, an extremely helpful tool which analyzes policies to detect weaknesses. The obvious weakness, of course, is that this policy allows the
data: URI. Data URIs allow HTML tags to be created with inline content, rather than reaching out to and making an additional request to the server. For example, an inline image might look like:
Since the CSP permits HTML tags to specify content via a
<script> tag - but we can’t use any of them, since our payload is included in the page using
data: URI, without using a
<script> tag. Additionally, it can’t load data from untrusted domains. (If you’re the impatient type, this is where you can skip to the end of the post to find out what ultimately worked.)
href attribute of an
a tag can be set from a data URI. However, navigation to data URIs is disabled in Chrome, Firefox, and IE/Edge for a number of years. This payload may still work in other browsers, but would require a link click. Given those restrictions, I decided it wasn’t the payload I was looking for.
<a href='data:text/html,<script>alert(1)</script>'>my link</a>
SVGs are commonly used to bypass XSS filtering. I thought they might work here, too, but inline scripts and event handlers I tried were unsuccessful.
<svg> <script>alert(1)</script> </svg>
<svg> <g><rect onclick="alert(1)" width="300" height="300"/></g> </svg>
<svg> <script src='data:,utf8;alert(1)'></script> </svg>
<script> tag which doesn’t execute with
innerHTML. My last attempt was embedding an SVG into the
data: URI of an
<img src="data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" > <g> 	<rect onclick="alert(1)" width="300" height="300"/> </g> </svg>" alt="">
With SVGs exhausted, I moved onto iframes. It was pretty straightforward to get an iframe to render, and I controlled the style and content as well. It probably wouldn’t have been too hard to use this to turn the site into a phishing page. However, I was having a lot of trouble getting scripts to execute, and SOP/CSP violations when trying to navigate the frame or main window.
<iframe src="data:text/html,<html><a href=example.com>click me!</a></html>"/>
At this point, I didn’t want to spend all my time testing only a single bug, so I wrote a short email to our internal mailing list to see if any of my coworkers had any ideas. The first break came from a coworker who suggested trying the
defer attributes for a script inside an iframe. This allowed me to get scripts executing inside the frame.
However, the origin of the iframe was the null origin. Basically, the browser treated the iframe as if it was loaded from a local file, clearing the iframe’s
After sharing those results with the thread, a second coworker came along to provide the ultimate solution. This solution uses the iframe’s
srcdoc3 attribute. The main difference in the
srcdoc attributes, in this case, is that
srcdoc uses the same origin as the embedding page (when used for an un-sandboxed iframe).
A Stack Overflow answer provides an explanation for the different attributes. Essentially, the
srcdoc attribute was added along with iframe sandboxing (the
sandbox attribute). Older browsers, which do not support the
sandbox attribute, would simply ignore it. As a result, sites which relied on this attribute would fall back to insecure behavior if they used the
src attribute. Using
srcdoc, on the other hand, meant that older browsers would simply render an empty iframe if sandboxing was not supported.
So here it is, an XSS payload which:
- Triggers when written with
- Doesn’t load data from external domains
I’m sure there are other payloads out there, so if you’re looking for a fun challenge, tweet me4 your own solution! I bet other HTML tags like
<canvas> have some fun properties to play around with.
Big thanks to Jeff Dileo and Andy Grant, who came up with the deferred script tag and
srcdoc solutions respectively - the client really appreciated knowing whether this was something that could be exploited practically.
eval()or included into a web page (the DOM) through a function that doesn’t perform output encoding.↩
Published date:  11 April 2019
Written by:  Tanner Prynn