Remote Image Upload Leads to RCE (Inject Malicious Code to PHP-GD Image)

Inject Malicious Code to PHP-GD Image

بسم الله الرحمن الرحيم

On this web application, there are two ways to add an image to media library, first one is using local file upload and the second one is remote file upload from a Stock Photo website.

The first thing that came to my mind was that there might have SSRF vulnerability through the remote file upload feature. But I think it worth to test the local file upload first.

After several test on the local file upload, I can concluded that the app only allows image files (.jpg, .png, .gif and .svg), the .svg one could leads to XSS.

Then I moved on to remote file upload from a Stock Photo website feature, tried add an image and intercept the request. I found out there are 3 POST form-data that will be send to server: name, url and photoId.

I tried to change the Url to my server, but got Internal Server Error response and no any incoming connection on my server. Then I tried to remove the PhotoId data and repeat the request, again got Internal Server Error response but I got incoming connection on my server. I just point the url to my host without valid image file, so this may cause the Internal Server Error.

Later, I tried to provide a url with valid jpg image file and it successfully uploaded to media library. Then with the same image file, I modify the “name” form-data to image.html and image.php and surprisingly both successfully uploaded.

Long story short, I realized that I can’t inject php script into image exif metada because the app recreate the fetched image file with PHP-GD lib, after image re-creation, the exif metada will be removed.

I found several articles told that we still can inject PHP script into image and will not removed after image processing.

According to three articles above, we still can inject php script to the image but only with limited characters.

Also there is payload injector created by dlegs.

First attempt, I use dlegs tool to inject payload to .jpg image. You need to recreate a .jpg image with php-gd first then inject the payload with

$ php gd.php image.jpg image-gd.jpg

Then inject the payload to image-gd.jpg

$ python image-gd.jpg ‘<?php phpinfo()?>’ image-gd-poc.jpg

To make sure the injected payload working, try re-create the image-gd-poc.jpg with php-gd.

$ php gd.php image-gd-poc.jpg image-gd-poc-1.jpg

Then compare binary image-gd-poc.jpg and image-gd-poc-1.jpg

I use vbindiff to compare both image.

$ vbindiff image-gd-poc.jpg image-gd-poc-1.jpg

The injector writed by dlegs will inject your provided payload into a .jpg image file. This is only working if your target app recreate the image with default quality (default -1). However on my targeted app, it convert the image with quality 90, so the payload injected on image will removed.

The first attempt with payload on .jpg image was failed, so I try payload by @ABOUL3LA with .gif image file. On his article he provided POC.gif image that already injected with <?phpinfo()?>.

Finally, this is what I got after upload POC.gif and renaming the form-data “name” to test.php

If you want to use POC.gif payload by @ABOUL3LA the target app must haveshort_open_tag=Onset in php.ini, otherwise the php script won’t executed and you have to modify the payload with <?php ?> tag to make it work.

Reported this to the bug bounty program and get triaged in just few minutes, and they made quick patch in few hours.

I explore bit more into the php-gd lib, just want to know how many bytes we can inject to image. After several testing on different .jpg and .gif image, I can conclude that .jpg image can be injected with payload up to 13 bytes, but .gif image could be injected with more bytes.

GIF image that could be injected is GIF image that has null byte blocks. I found out that GIF images with Netscape Looping Application Extension have this null byte blocks.

Netscape Looping Application Extension is the most popular Application Extension Block that tells browser or other GIF viewer to loop the entire animated GIF file.

This is example GIF image that has Netscape Looping Application Extension

If we recreate the GIF image with php-gd, the null byte blocks not removed. Look at binary comparison between original gif and gd gif below.

So let’s try inject php shellcode to this null byte blocks and recreate the GIF image with PHP-GD, this is the binary comparison:

The injected payload remain there. Then you can upload the gif image to your vulnerable target app.

You can get the GIF Injector and other scripts here:

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store