Update: Below is a list of people who solved the CTF. Congrats folks!
- Dixie Flatline
- Cernica Ionut
- Dario D. Goddin
This post is the second part of the Bypassing PHP Null Byte injection protections blogpost. If you want to try the CTF first before going through the write up, head to the link first. Otherwise, keep on reading :)
The main trick described in this write-up relies on the fact that a Local File Include (LFI) vulnerability is exploitable but with some restrictions imposed by the code. Among these restrictions, there is some active filtering on Path Traversal. Name;u, an image file extension (.png) is always appended to the successfully uploaded files. In addition, the server is running an up to date version of PHP which is not vulnerable to the well known Null Byte Injection trick.
To bypass these restrictions and successfully achieve Remote Code Execution chaining through the aforementioned LFI vulnerability, one can use one of the built-in PHP Wrappers as described in detail on the next section of this write-up.
When visiting the URL of the vulnerable application one can see that it is a web app for uploading pictures:
Following the normal application flow first, I tried to understand how the application behaves and how the uploaded files are being parsed by the code with some simple tests:
- Upload a true JPEG image file with an arbitrary dimension. Error: Failed to load image
- Upload a true JPEG image file with a .png extension appended to the filename in an arbitrary dimension. Error: Failed to load image
- Upload a true JPEG image file with a .png extension appended to the filename with a 100×100 ppi dimension. Error: Failed to load image
- Upload a true PNG image file in an arbitrary dimension. Error: Invalid image dimensions
- Upload a true PNG image file in with 100×100 pixels dimension. Passed
When the application accepts the file it renames it to an apparently random filename and appends the .png extension to the file inside the “uploads” directory, as shown below:
My first assumption was that there was no simple way of bypassing the parsing of the uploaded files with something like imagecreatefrompng(). I feared that I would need to do something like this to be able to upload my PHP backdoor. Thankfully it was (somewhat) simpler than that.
Next thing was checking if the application for some reason blindly trusts the MIME type sent by the browser to trigger the imagecreatefrompng() function by uploading an arbitrary file:
And yes, by simply tampering with the Content-Type field on the body of the POST request we can successfully upload an arbitrary file. In this case, we’ve uploaded a simple PHP backdoor. But unfortunately, we’re not able to execute it as the file is renamed and the PNG extension is added as seen below:
I tried the obvious Null Byte Injection by appending quite a few encoded variations of the “%00” string to the filename on the “upload” and “view” functionalities of the application to overcome this limitation but after a while I’ve noticed that the server was running PHP v5.5.9 by relying on the X-Powered-By header highlighted on the server response above, which is not vulnerable to this over a decade old widely known vulnerability.
This got me stuck for a while, when I took a step back to see what I could have missed. A server response while attempting a basic LFI got my attention:
After a series of attempts to bypass this active filtering, I ended up trying the PHP Filter trick in order to successfully achieve LFI:
This allows us to include and Base64 encode arbitrary files. This gets us somewhere, as we can simply abuse this functionality in order to read the source code hosted on the server. This is shown in the index.php code below:
But we are still restricted from using well known tricks to traverse the path. We can see clearly why on line 6 of the “index” code above. This restriction got me stuck for quite a while. And that’s when the aforementioned PHP Wrappers came to rescue.
PHP comes by default with several built-in wrappers for various URL-style protocols for use with the filesystem functions. Among those there are the compression wrappers such as zip://. This wrapper could allow one to unzip archives on the fly while providing access to the files stored in the archive. So if you abuse that LFI with a URL-style protocol such as zip://archive.zip#file.php you could potentially bypass this restrictions imposed by the code by uploading a ZIP archive with your PHP code inside of it and then executing it. Let’s see how this could work on our scenario, step-by-step:
First we set a very simple PHP script. It will take from the $_GET superglobal parameters that were sent in HTTP request using GET method. The “param1” and “param2” will be interpreted respectively as a function name and arguments for the function to be executed. Next step is to create a ZIP archive containing our PHP script:
Then we upload this file to the server by using the aforementioned trick of tampering with the Content-type. Now we can easily achieve RCE abusing the wrapper:
There’s an actual flag file located on the server to defeat this challenge which will be left as a trivial exercise to the reader :)
How to prevent this from happening on your web application
The main problem with this sample application is the fact, it allows code inclusion from external resources and it doesn’t validate user input enough. Using include($_GET[‘file’]); is not a good idea.
Although the application was “safe” from Null Byte Injection as it was running a version in which this bug has been fixed, it was still possible to go from LFI to RCE using the aforementioned tricks.
That said, in order to provide a defence in depth control into the web application, such as restricting RFI by setting allow_url_fopen=off and allow_url_include=off. It might also be a good idea to unregister all the URL wrappers that you don’t intend to use in the code such as the example below:
Part of the code used in the PandaUploader was inspired in the pixelshop challenge from plaidctf-2016 by PPP.