Recently I saw a feature on a product I work on where we allowed hotlinking to arbitrary gifs without pulling them in, mangling, and then saving for our own use. Right away I thought, “Well this isn’t wise” and set off to find ways to abuse it. The easiest and most obvious was to link to an image and then swap it out for a less savoury one later. Kid stuff, right? Let’s do some real damage. Spoiler alert: I made a really cool thing but didn’t get to weaponize it the way I wanted to.
Getting down to business
As always, research starts with Google and IRC. I had a feeling that something like this had been done before, so I set about finding papers, posts, or anything really that pointed me to what I was looking for. In short order, I found a couple of good resources that explained the attack and how to craft a malicious gif.
The basic idea is to use ASM and manually assemble a GIF, filling in the required header and fields. By setting the width to a specific value of
10799, when the gif is rendered as a script, this is interpreted as a
As a product of my own stupidity and failure to follow directions, I mistakenly assumed for a bit that the recommended compiler, yasm, was Windows-only. After far too long fighting YASM and C++ runtime trying to get the stupid thing to work, I noticed that I could just pull the source and compile it on my local machine. Sweet! After that, compiling this was a breeze:
$ yasm ./gifjs.asm -o img.gif
Execution and Exploitation
Unfortunately, here’s where things get a little sad. In order to actually get this to work, I had to do it under some contrived conditions that, while not impossible, are unlikely (in my opinion).
- You have to have the gif interpreted with
<script>tags as opposed to
- You have to send a misleading MIME type
These two conditions mean that it’s unlikely you will find something in the wild which you can abuse. You’re better off using a server you already control and setting up explotation conditions favorable to yourself.
To get this to run, I wrote the following tiny bit of HTML:
This is a test <img src="img.gif"> <script src="img.gif"></script>
As you can see, it just throws up a bit of test, displays the malicious GIF as an image, and then again as a script. If you pop open your browser and go to the file in your local filesystem (say,
/tmp/test.html for instace), the gif will pop the alert box for you. Fun, right?
Now try uploading it to an image hosting site such as Imgur and sourcing it from there and you’ll notice something interesting happens. Or rather, doesn’t happen. If you try to run the above HTML but using a direct link to a .gif on imgur instead, you’ll notice that your browser’s console likely displays an error saying something to this effect:
Refused to execute script from 'https://i.imgur.com/IXGn93f.gif' because its MIME type ('image/gif') is not executable.
Detour: what the heck is a MIME type?
MIME types aren’t really anything more complex than a label that gets stuck onto some data to tell the receiving end what type of data it is. This lets the client know how it can handle the data. This is just a label and is built on trust. What do we do with trust? We abuse it. Back to your regularly scheduled programming.
This brings me back to point #2: You need to send a misleading MIME type to convince the browser to execute your file. We already know that Imgur won’t allow us to do that, so how do we do it? In my case, I used a simple bit of python.
import SimpleHTTPServer import SocketServer PORT = 8000 class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler): pass Handler.extensions_map['.gif'] = 'application/octet-stream' httpd = SocketServer.TCPServer(("", PORT), Handler) print "serving at port", PORT httpd.serve_forever()
This uses SimpleHTTPServer, which is already in Python’s standard libraries, to serve the contents of the local directory. By default, SimpleHTTPServer will try and give things appropriate MIME types based on extensions, so we add a smal change to tell it to interpret
.gif extensions as
application/octect-stream, which browsers will execute. If I named that html file as
index.html, I can now hit
http://127.0.0.1:8000/index.html and get our malicious gif served back with a MIME type that it is okay with executing. The result? We run the JS compiled into the GIF.
This is not a new or novel attack. This is also not something I feel is widely exploitable, but it is fairly sneaky and exposes a few areas of trust that we can abuse.
- Browsers do little in the way of actual heuristics when trying to determine file type. At best, they will look at the extension and magic byte to try and determine if the file is what it claims to be.
- This is a valid GIF and a valid bit of JS, so heuristics would have to be more sophisticated to catch something like this.
- Browsers trust MIME types perhaps a bit too much.
- This would be easy to exploit and hard to detect using a site you control, since users wouldn’t be able to see the JS that is being executed. Then again, they might see that
something.jswas being executed but might not be able to GET the file to see what is in it. Obfuscation level: 3/10.
It’s interesting. It’s fun. It’s simple.
https://ajinabraham.com/blog/bypassing-content-security-policy-with-a-jsgif-polyglot https://gist.githubusercontent.com/ajinabraham/f2a057fb1930f94886a3/raw/62b8e455ad62c42222de9059cd0d20c1a79e2cdb/gifjs.asm http://blog.portswigger.net/2016/12/bypassing-csp-using-polyglot-jpegs.html