DEV Community

Cover image for This Image Is Also a Valid Javascript File
Sebastian Stamm
Sebastian Stamm

Posted on

This Image Is Also a Valid Javascript File

Images are usually stored as binary files, while a Javascript file is basically just text. Both have to follow their own rules: Images have a concrete file format, encoding the data in a certain way. Javascript files have to follow a specific syntax in order to be executable. I wondered: Can I create an image file that has valid Javascript syntax, so it is also executable?

Before you read on, I highly recommend checking out this code sandbox with the result of my experimentation:

If you want to check out the image and inspect it yourself, you can download it here:

Choosing the Right Image Type

Unfortunately, images contain a lot of binary data which will throw an error if interpreted as Javascript. So my first idea was: What if we just put all the image data in a large comment, like this:

Enter fullscreen mode Exit fullscreen mode

That would be a valid Javascript file. However, image files need to start with a specific sequence of bytes; a file header that is specific to the image format. PNG files for example always have to start with the byte sequence 89 50 4E 47 0D 0A 1A 0A. If an image would start with /*, it would not be a valid image file anymore.

This file header lead to the next idea: What if we could use this byte sequence as variable name and have a huge string assignment like this:

Enter fullscreen mode Exit fullscreen mode

We are using template strings instead of the normal " or ' strings because the binary data could contain linebreaks and template strings are better at dealing with those.

Unfortunately, most byte sequences for image file headers contain unprintable characters that are not allowed in variable names. But there is one image format we can use: GIF. The GIF header block is 47 49 46 38 39 61, which conveniently spells GIF89a in ASCII, a perfectly legal variable name!

Choosing the Right Image Dimensions

Now that we found an image format that starts with a valid variable name, we need to add the equals and backtick characters. The next four bytes of the file are therefore: 3D 09 60 04

First bytes of the image

In the gif format, the four bytes following the header specify the dimensions of the image. We have to fit 3D (the equals sign) and 60 (the backtick that opens the string) in there. GIF uses little endian so the second and fourth character have a huge influence on image dimensions. We want to keep them as small as possible to not end up with an image that is tens of thousands pixels wide. Therefore we store the big bytes 3D and 60 in the least significant bytes.

The second byte of the image width needs to be a valid whitespace character, because this would be the space between the equals sign and the beginning of the string GIF89a= `.... Keep in mind that the hexcode of the characters should be as small as possible, or the image would be huge.

The smallest whitespace character is 09, the horizontal tab. This gives us an image width of 3D 09, which in little endian translates to 2365; a bit wider than I would have liked, but still reasonable.

For the second height byte, we can just choose something that produces a nice aspect ratio. I chose 04, which produces a height of 60 04, or 1120.

Getting our own script in there

Right now, our executable gif does not really do anything. It just assigns a large string to the global variable GIF89a. We want something interesting to happen! Most of the data inside the GIF is for encoding the image, so if we try to add Javascript in there, we would probably end up with a very corrupted image. But for some reason, the GIF format contains something called a Comment Extension. This is a place to store some metadata that will not be interpreted by the GIF decoder - a perfect place for our Javascript logic.

This comment extension comes right after the GIF color table. Since we can put any content in there, we can easily close the GIF89a string, add all our Javascript and then start a multiline comment block, so the rest of the image does not interfere with the Javascript parser.

All in all, our file could then look like this:



Enter fullscreen mode Exit fullscreen mode

There is a small restriction: While the comment block itself can be of any size, it is composed of multiple sub-blocks, each of which has a maximum size of 255. Between the subblocks is a single byte that indicates the length of the next subblock. So in order to fit a larger script in there, it has to be divided into smaller chunks, like this:

alert('Javascript');/*0x4A*/console.log('another subblock');/*0x1F*/...
Enter fullscreen mode Exit fullscreen mode

The hexcodes in the comments are the bytes that indicate the size of the next subblock. They are irrelevant for the Javascript, but required for the GIF file format. In order to prevent them from interfering with the rest of the code, they have to be in comments. I wrote a small script that processes the script chunks and adds them to the image file:

Cleaning up the Binary

Now that we have the basic structure down, we need to make sure that the binary image data does not ruin our syntax. As explained in the previous section, the file has three sections: The first one is an assignment to the variable GIF89a, the second one is the Javascript code and the third one is a multiline comment.

Let's have a look at the first part, the variable assignment:

Enter fullscreen mode Exit fullscreen mode

If the binary data would contain the character ` or the character combination ${ we are in trouble because this would either end the template string or produce an invalid expression. The fix here is quite easy: Just change the binary data! E.g. instead of using the ` character (hexcode 60), we could use the character a (hexcode 61). Since this part of the file contains the color table, it would result in some of the colors being slightly off, e.g. using the color #286148 instead of #286048. It is unlikely that someone will notice the difference.

Fighting the corruption

At the end of the Javascript code, we opened a multiline comment in order to make sure the binary image data does not interfere with the Javascript parsing:

alert("Script done");/*BINARY IMAGE DATA ...
Enter fullscreen mode Exit fullscreen mode

If the image data would contain the character sequence */, the comment would end prematurely, which would result in an invalid Javascript file. Here again we can manually change one of the two characters so they no longer end the comment. However, since we are now in the encoded image section, this will result in a corrupted image, like this:

Corrupted Image

In extreme cases the image could not be displayed at all. By carefully choosing which bit to flip I was able to minimize the corruption. Fortunately there were only a handful of instances of the harmful */ combination to deal with. There is still a bit of corruption visible in the final image, e.g. at the bottom of the "Valid Javascript File" string, but overall I am pretty happy with the result.

Ending the File

The last operation we have to perform is at the end of the file. The file has to end with the bytes 00 3B. So we have to end our comment earlier. Since this is the end of the file and any potential corruption would not be very visible, I just ended the multi block comment and added a single line comment so that the end of the file would not cause any problems when parsing:

/* BINARY DATA*/// 00 3B
Enter fullscreen mode Exit fullscreen mode

Convincing the Browser to Execute an Image

Now, after all this, we finally have a file that is both an image as well as a valid Javascript file. But there is one last challenge we have to overcome: If we upload the image to a server and try to use it in a script tag, we would most likely see an error like this:

Refused to execute script from 'http://localhost:8080/image.gif' because its MIME type ('image/gif') is not executable.

So the browser rightfully says "That's an image! I am not going to execute that!". Which in most cases is a good mindset to have. But we want to execute it anyway. Our solution here is to just not tell the browser that it's an image. For that I wrote a small server that serves the image without any header information.

Without the MIME type information from the header, the browser does not know that it is an image and just does what fits the context best: Display it as image in an <img> tag, or execute it as Javascript in a <script> tag.

But ... why?

That is something I have yet to figure out. It was a nice mental challenge to make this stuff work, but if you can think of any scenarios where this might actually be useful, please let me know!

Top comments (19)

ben profile image
Ben Halpern


dploeger profile image
Dennis Ploeger

That is pretty awesome. Thanks. I'm thinking about possible security implications of this. Just like when PDFs got hacked. But the concept is different here. It would be worse if the file would be in an img tag and the browser would display it and run the JavaScript code.

cyril_ogoh profile image
ogoh cyril

This post is a zero day attack 😂😂😂

But we want to execute it anyway. Our solution here is to just not tell the browser that it's an image. For that I wrote a small server that serves the image without any header information

Its a nice project tho

vsetka profile image
Vladimir Šetka • Edited

How is it a zero day attack? The <img> tag will never execute its content as javascript, regardless of the response headers.

etienneburdet profile image
Etienne Burdet

Wow, really… cool… 🤔 … scary?
One use could be to embed trackers in image files (giphy does that already maybe?). The other one would be straight hacking.

None of that sounds "great user experience", but it's good to know it's a thing 😬

vsetka profile image
Vladimir Šetka

How would you embed a tracker? The code won't execute unless it's in a script tag, at which point you might as well just load javascript.

If you're talking about embedding information in an image, there's already ways to do that (search for steganography).

etienneburdet profile image
Etienne Burdet • Edited

Well I don't know precisely, but I was thinking something around a giphy copy-paste style: either with iframe, or little bit of JS script that seems to load a valid image. Like "copy this <script> … </script> at the bottom of your page and insert your .gif URL", which seems to be perfectly valid .gif if you open in browser.

Or a service like cloudinary could do that: inject script in your images and then execute it inside their SDK (while the url still gives a perfectly valid image).
That seems over complicated though…giphy just juste plain iframe without hiding anything and nobody cares 😅

jabo profile image

This is awesome! btw nice snake game 👀

phlash profile image
Phil Ashby

From a previous conversation:

For the ultimate in insane polyglot'ness, check out POC||GTFO publications ( where Ange Albertini ( officially does voodoo.

lookrain profile image
Lu Yu

Sorry if I missed something from the article, but did you successfully execute some JS from an tag in the end? It looks like that wouldn't be achievable?

sebastianstamm profile image
Sebastian Stamm

My goal was not to execute code from an img tag, my goal was to create a file that is both an image and a script. Using the same file, the browser shows it as image in an img tag (without executing any Javascript), and executes the Javascript in a script tag (without showing any image).

lookrain profile image
Lu Yu

Ah i see. Very interesting and informative article!

michelcpp profile image

Wow that's cool

juanfrank77 profile image
Juan F Gonzalez

This is actually very interesting! Although the fact of making the browser do things that it wouldn't do otherwise makes me think it would be used by someone somewhere in a malicious way.

niweera profile image
Nipuna Weerasekara

Thanks this made my day

amberisvibin profile image
Amber 🏳️‍🌈

Wow, that's incredible.

koas profile image

Awesome! 👏🏻👏🏻👏🏻👏🏻

adriangrigore profile image
Adrian Emil Grigore

Pretty cool hack! Nice!

sharadcodes profile image
Sharad Raj (He/Him)

Damn this is 👀