In this lab, we will bypass simple file validation to upload PHP code. I found this lab particularly interesting because the bypass involved injecting code into an image’s metadata. This is a technique I was unfamiliar with before attempting to solve this lab and I thought it was pretty cool. So, let’s get into my thought process for solving this lab!
The task at hand
The description of the lab is as follows:
“This lab contains a vulnerable image upload function. Although it checks the contents of the file to verify that it is a genuine image, it is still possible to upload and execute server-side code. To solve the lab, upload a basic PHP web shell, then use it to exfiltrate the contents of the file
/home/carlos/secret. Submit this secret using the button provided in the lab banner. You can log in to your own account using the following credentials:
In the directions, we’re given the hint that the code validates an image based on its contents. The first thing that came to mind was changing the magic bytes of my PHP web shell to pass it off as an image. So, I set out to do just that!
Magic Bytes are file signatures used to identify files. They are used in UNIX-based operating systems to distinguish different file types from each other. Operating systems need to know this because different file types need to be handled differently. For example, an image file must be treated differently than an executable file. Windows, on the other hand, relies on file extension names instead.
So, if the server that is doing the file validation is running Linux, we might be able to trick it into thinking that our PHP file is an image by changing the leading bytes.
Let’s craft the payload!
Since we’re given the directory of the secret, my PHP code just outputs the file’s contents to the browser. For convenience’s sake, it’s not a full-blown web shell:
$file = '/home/carlos/secret';
$contents = file_get_contents($file);
I have it saved as
webshell.php (super low profile). Using the
file command in my terminal, we can see that it gets recognized as a text file with PHP code written in it:
Even after we change the extension, it’s still recognized as a PHP script. Magic Bytes!
Let’s see what happens when we change the leading bytes of the file to the file signature of a JPEG.
First, let’s use
xxd to see what the original bytes look like:
For this to be recognized as a JPEG, we’ll have to change the leading bytes to
FF D8 FF E0. I got that magic number from this wiki article.
To edit the file, I’m going to use a tool called
hexedit. Before editing, I’ll add a few new lines to preserve the original code (4 to be exact because the signature is 4 bytes):
Now, after saving, the file should be recognized as a JPEG:
Just like magic!
Logging into our lab’s account with
wiener:peter, we are met with an option to upload an avatar image. Proxying my traffic with Burp, I try to upload the web shell:
No dice! Looks like the file checker doesn’t just check file’s signature…
We need to find another way to pass our PHP code off as an image file. My second thought was to somehow inject the PHP code into a valid image instead of trying to make my PHP file mimic an image.
A quick Google search on “injecting PHP code into an image” lead me to this excellent article. Apparently, code written into the metadata of an image can be executed!
If you don’t know what EXIF data is, it’s basically metadata, or extra data written to an image to provide information about the context of the image. This can include the device used to snap the photo, the resolution, and even more revealing information like the location the image was taken.
For this reason, examining EXIF data is a powerful recon technique. Let’s take a look at an example of a random picture I downloaded online:
This information is not only readable but writeable as well.
We can add comments in the metadata with a tool called
jhead. Using the command
jhead -ce <image>, we can edit the EXIF comment field:
Saving this, our metadata now stores our PHP code in the comment section of the metadata:
This has no impact on the integrity of the file and will bypass most image upload checks:
The only caveat is that most servers aren’t configured to execute image files (makes sense). So, in order for the code to run, we’ll have to change the filename to have a
Inspecting the uploaded image with dev tools tells us where the file was uploaded to:
Browsing to this location gives us the secret! Of course, I’m not going to show this to you. You’ll have to take my word for it and solve the lab on your own 😉
Of course, this would only work if the server doesn’t validate files based on their extension. There are workarounds to bypass this even in the event that the server does make this check (like exploiting a race condition) so it is up to you to get creative with this and find some critical bugs.
Anyways, thanks for reading! I hope you learned something because I certainly have! Until next time 🙂