Author: Daniel Kalinowski
- PHP with gd module enabled (sudo apt install php-gd)
- PoC from our article was tested on PHP 7.3.10-1 (cli) (built: Oct 8 2019 05:24:47) (NTS)
- this zip archive
- some free time
Every graphic file consists of pixels. If you open an image file in a hex editor you will immediately notice that those pixels are nothing more than just bytes. Moreover, you can create image with particular byte order that will form malicious payload as bytes’ representation. For example: 3C 3F 20 70 68 70 69 6E 66 6F 28 29 3B 20 3F 3E is the equivalent for <? phpinfo(); ?>.
3C 3F 20 70 68 70 69 6E 66 6F 28 29 3B 20 3F 3E < ? p h p i n f o ( ) ; ? >
If you could upload a previously prepared image on the server as 1:1 copy with a .php extension it would mean that you can easily gain RCE on the server. Most of the web applications, however, show that it is rather unlikely to happen. They usually perform some image manipulation as cropping, compression etc., thus "destroying" the malicious input.
Our code, that we will be using to simulate server behaviour, looks like this:
It loads an example.png file via imageCreateFromPng function, processes it with imagepng function and saves the results to a new.png file.
Every PNG file consists of chunks which are defined as below:
A sample research about particular iDAT chunk is extensively described in "Encoding Web Shells in PNG IDAT chunks" article. In this blog post, we'll consider if there are more chunks which may happen to be useful to smuggle PHP payload.
Besides critical chunks there are also extensions chunks that have various types and uses.
The first what comes into mind is an extension chunk called tEXt. This chunk seems to be a perfect candidate for our test. Its purpose is to store text in the PNG file. Let's give it a try then!
Generate the sample file and validate it via ImageMagick :
[email protected]:~/image-ip/png$ convert -comment "<?php evil();?>" -size 10x10 xc:white example.png [email protected]:~/image-ip/png$ identify -verbose example.png | grep comment comment: <?php evil();?>
Run our server script (code is available in the first paragraph) and check what happens with the payload.
Unfortunately, our PHP payload did not survive imagepng function processing. The reason of this is that all textual chunks are optional. As you have seen above, not every chunk is a perfect candidate to smuggle our payload. In the rest of this article, we'll talk about the chunks which won't be modified by imageCreateFromPng and imagepng functions. Let's focus on PLTE chunk then.
PLTE chunk is responsible for storing the information about colour palette used in the image. Let's see what we can do with the structure of this chunk.
We have to generate a new image, this time with the help of PHP. We will use the code snippet available below:
Example structure of PLTE chunk may looks like this:
- <Length> - 00 00 00 03
- <Type> - 50 4C 54 45 (PLTE)
- <Data> - DE AD FE
- <CRC> - 61 C1 38 5F
As you can see the <Data> part of the chunk is equal to our input from imagecolorallocate function.It means that we can convert our payload to hex, write it to PLTE chunk as corresponding colours and then create palette-based image file.
Take a notice that a chunk length must be divisible by 3. We need to add an additional character to our payload (e.g. a whitespace): "<?php phpinfo()?> ". 18 characters = 18 bytes = 6 colours = RGB(3C,3F,70),RGB(68,70,20) etc.
Let's generate an image file with our defined PLTE chunk.
[email protected]:~/image-ip/png$ php createPNGwithPLTE.php [email protected]:~/image-ip/png$ file example.png example.png: PNG image data, 7 x 2, 4-bit colormap, non-interlaced [email protected]:~/image-ip/png$
As you can see our payload is still there! Happily, we can do even more. We will add a new compression level for created images to simulate image transformation on the server side.
Take a look at server.php and change imagepng($im,"new.png"); to imagepng($im,"new.png",9);.
Now run the server.php and examine the results.
As you can see our payload is still there again. With this method you can upload a malicious image that will survive imageCreateFromPng and imagepng function-calls.
As a bonus, in this paragraph we'll consider the similar scenario with imagecreatefromgif.
The Graphics Interchange Format is defined in terms of blocks and sub-blocks which contain relevant parameters and data used in the reproduction of a graphic.
Extension Blocks – Extensions are defined using the Extension Introducer code to mark the beginning of the block, followed by a block label, identifying the type of extension.
In GIF two extension blocks are used for text storage. Comment Extension block and Plain Text Extension. Sadly, all extension blocks are optional therefore they will not survive imagecreatefromgif function processing. If you want to bypass this function you need to use Global Color Table method.
Basically, here we will do the same thing that we did in the PLTE PNG chunk. We are going to convert our payload into hex-string and then assign specific colours to the colour table.
Global Color Table block contains a colour table which is a sequence of bytes representing red-green-blue colour triplets. Here we also have to ensure that the payload is divisible by 3.
Let's create a file.
Open the generated file inside hex editor.
[email protected]:~/image-ip/gif$ php createGIFwithGlobalColorTable.php [email protected]:~/image-ip/gif$ ghex example.gif
Run server.php file and lookup new.gif file inside the hex editor.
As you can see our payload has been saved to new file.
- Make sure that the web server does not parse or execute any code in the directory which stores user-uploaded images/files. Turn PHP engine off (‘php_flag engine off’ entry in your configuration file) and disable any other additional interpreters/parsers (as CGI). Also, confirm that the web server does not try to parse or execute unrecognised file-types/file extensions.
- If it’s possible – store user-uploaded images outside of the web-root directory or put them on different/additional (CDN) server. Keep in mind that if your web application is vulnerable to LFI (Local File Inclusion) then uploading images outside of the web-root won’t protect you.
- Always properly validate file names (if it’s possible save user-uploaded files with randomly generated names). Whitelist acceptable file extensions.
- Malicious payload may not only contain a payload which will be parsed by the server (and give the attacker a chance to execute his/her code on the server). Payload may also be parsed by the web-browser which could lead to a Cross-Site Scripting vulnerability in your web application. To prevent XSS attacks enforce proper Content-Type header while accessing user-uploaded resources. Don’t forget about X-Content-Type-Options: nosniff, to mitigate MIME-sniffing behavior in some (older) browsers.
Presented methods are not universal but they allow to bypass some validation checks during image file processing. I hope that this article will help you find more vulnerabilities in the future :)