Chrome SOP-Bypass with SVG

post thumb
Vulnerability
by Christian Schneider / on 12 Jul 2014

Archived Blog Post from 2014

Anatomy of my Chrome SOP-Bypass

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 the 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 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 on 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.

OK, so let’s try that: I first used <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 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 are 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 an email 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.

Google Security rewarded the responsible disclosure of this Chrome vulnerability along with a fully documented working exploit with a bug bounty. Thank you very much Google!

Conclusion

As a Pentester

As always think outside the box and try to combine different tricks and techniques to get the full working exploit. As done here with the fact that images can also be loaded indirectly via SVG and when combining this with browser cache to reset the tainted flag, it was a successful end-to-end SOP bypass for exfiltration of secret images in Chrome.

As a Developer

At first sight this was more of a browser vendor issue than an application developer’s. But nevertheless it shows how underestimated recommendations, like using proper anti-caching response headers for sensitive content, can sometimes prevent successful 0day exploitation, as was the case with this SOP bypass: I’ve successfully tested that images with proper anti-caching headers won’t get exfiltrated that way (as the cache requirement for resetting of the tainted flag was no longer met).