Mr. Burns HackTheBox Write-up

d4rkstat1c
7 min readJun 15, 2021

A PHP security CTF providing more realistic methods and approaches to overcome obstacles to reach a final goal (command execution), this challenge is strikingly similar to ImageTok (code-base wise), however containing very different bugs.

0x01: Including all possibilities.

This challenge, similar to ImageTok allows the CTF player to download the code-base of the application to analyze the source code to discover exploitation possibilities. When examining the code-base I immediately noticed this web-application contains very similar PHP code to that of ImageTok’s code-base. Since I have a fairly decent knowledge of PHP application security and web-application development in PHP and my memory of ImageTok’s code-base was still fresh I took notice of some interesting logic within the Router.php file:

Router.php’s getRouteParameters function.

I immediately noticed that there’s a obvious difference in logic to that of ImageTok’s code base, the function is calling urldecode twice on the url’s URI/path.

I examined the code further and took notice of something interesting in MinerController.php:

Now things started to add up, did you spot the vulnerability? File inclusion at its finest, simply double url-encoding the URI/path should allow an attacker to bypass the routing parser/filter. So theoretically I should be able to gain LFI by utilizing the following URI/path payload:

/miner/..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252Fetc%252Fpasswd

However after attempting to exploit the LFI to include the /etc/passwd file I received the following error:

Ah, open_basedir restrictions are in play (limited PHP filesystem access), this can be bypassed using symlinks via PHP’s symlink function if we can achieve arbitrary writes to PHP-RCE somehow. So I begun analyzing DockerFile and entrypoint.sh file to see the PHP configurations and web-server setup and I noticed:

Ok so we can access the /tmp directory, so I noticed within the entrypoint.sh file that a file is indeed written to /tmp that we can test our LFI bug against:

So I decided to attempt to read this file via LFI with success:

/miner/..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252Ftmp%252Fdemo_miner.log

So my attention shifted completely towards discovering a method to achieve arbitrary writes so that ultimately I could gain php level RCE via LFI. I begun examining the /info endpoint (phpinfo’s output) for more clues. I discovered something very interesting:

session.upload_progress.cleanup is set to Off this opens the PHP session file LFI to RCE attack vector. This is because PHP will store its session files with the filenames as sess_<ID> within the following directories:

/tmp/sess_<id>
/tmp/sessions/sess_<id>
/var/lib/php/sess_<id>
/var/lib/php/sessions/sess_<id>
/var/lib/php<version>/sess_<id>
/var/lib/php<version>/sessions/sess_<id>
etc..

And will not delete them directly after writing them, indicating that we can either access the session files via symlinks or we’ll luck out and the session files will be written to the /tmp directory due to the fact that session.auto_start is set to Off:

We cannot target the current PHP session cookie because there isn’t one set however if we provide the parameter
PHP_SESSION_UPLOAD_PROGRESS in multi-part POST data we can force the setting of a PHPSESSID cookie. This will also write whatever the Cookie HTTP header’s PHPSESSID value is set to, to the directory the session files are written to as the filename and any value provided to
PHP_SESSION_UPLOAD_PROGRESS will be the session file’s contents. This will also allow us to also bypass the preset HTTP GET request methods check for the /miner route/endpoint in index.php:

This will theoretically enable us to write arbitrary PHP code located within the session file to a arbitrary directory.

0x02: Sessions protect and serve.

In order for us to include the session file we must first establish that they’re indeed being written to the /tmp directory. I used curl to check for the existence of session files via exploiting the method explained in 0x01:

curl http://<IP>:<PORT> — cookie ‘PHPSESSID=test’ -F ‘PHP_SESSION_UPLOAD_PROGRESS=<PHP_CODE>’ -F ‘file=@junk_file

curl http://<IP>:<PORT>/miner/..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252Ftmp%252Fsess_test

I found nothing initially, I wondered if perhaps the file was being deleted automatically so I copied and pasted both curl commands consecutively to discover that there is indeed session files being automatically removed and to my astonishment a session file appeared:

upload_progress_<PHP_CODE>|a:5:{s:10:”start_time”;i:1623754711;s:14:”content_length”;i:342;s:15:”bytes_processed”;i:342;s:4:”done”;b:1;s:5:”files”;a:1:{i:0;a:7:{s:10:”field_name”;s:4:”file”;s:4:”name”;s:9:”junk_file”;s:8:”tmp_name”;s:14:”/tmp/phpAelEHl”;s:5:”error”;i:0;s:4:”done”;b:1;s:10:”start_time”;i:1623754711;s:15:”bytes_processed”;i:17;}}}

<PHP_CODE> is where we can inject arbitrary PHP code to achieve php code RCE, however we’ll need to exploit a race condition as well to beat the file deletion which is easy to automate within a python3 script.

0x03: Escalating PHP-RCE to command execution.

So we are limited to what PHP functions we can execute due to disable_functions restrictions according to the DockerFile:

Disable functions setup within the DockerFile

This means we cannot directly achieve command execution via system and its cousins, so we will need to abuse something else entirely. Also putenv is disabled so utilizing the LD_PRELOAD environment variable to gain command execution is not possible within this challenge. There are two methods for gaining RCE via bypassing disable_functions in this challenge that I discovered. I’ll detail the intended path and the unintended path. The first path to bypass disable_functions is revealed within the config directory of the code-base. This challenge is running a nginx web-server and using FastCGI as its process manager. A common technique to bypass disable_functions is to abuse FastCGI, there’s a few different methods to do this I was unable to exploit most however I achieved command execution via the FastCGI extension/shared library method. In theory when an attacker provides an argument to the extension parameter to PHP_ADMIN_VALUE within a FastCGI client request:

PHP_ADMIN_VALUE’: ‘extension = /tmp/exec.so

FastCGI will dynamically load this library allowing for an attacker to provide a arbitrary/malicious shared library that can enable a variety of different attack vectors, including command execution. If an attacker can somehow write/upload a shared library file to the server’s disk, they can then achieve RCE. Shared libraries are not directly executed unless a constructor function is present, so we should theoretically be able to provide a custom .so file that executes calls the execve or system functions to execute the readflag binary and redirect its output to the /tmp directory within its constructor function, and by using LFI, effectively read its contents. The FastCGI payload request should look like the following:

However when building the PHP payload to generate the above request I noticed fsockopen was restricted due to disable_functions this can easily be bypassed with stream_socket_client and its cousins. They essentially perform the same logic as fsockopen, so our to generate the above request should look like the following:

<?php $fp = stream_socket_client(“unix:///run/php-fpm.sock”, $errno, $errstr, 30); fwrite($fp, base64_decode(“<b64_fcgi_payload>”)); ?>

The shared library should look like the following:

I’ll automate the process of payload generation using a modified version of Python-FastCGI-Client within my ctfs repo, including a python3 exploit script.

I.e. the process is as follows:

  1. With the PHP_SESSION_UPLOAD_PROGRESS session file arbitrary write method write a PHP payload to /tmp that writes the shared library to the /tmp directory.
  2. Execute the payload generated from the last step via LFI.
  3. Generate a FastCGI payload that sends a FastCGI client request to /run/php-fpm.sock which will in turn execute our exec.so’s constructor function effectively executing the readflag binary and redirects the output of the readflag binary to /tmp/flag via system/execve.
  4. Write the generated FastCGI payload to the /tmp directory using the same method as step 1.
  5. Execute the FastCGI payload via LFI.
  6. Read the contents of /tmp/flag via LFI.

Method 2: Via mail()/sendmail.

By utilizing the PHP_SESSION_UPLOAD_PROGRESS + session file arbitrary write one can write a session file containing a PHP payload with the following contents to the /tmp directory:

<?php file_put_contents(“/tmp/rce.sh”, “/./readflag > /tmp/flag”); ?>

Afterwards you can execute the session file PHP payload via LFI.

Using the above technique again one can execute the following payload leading to command execution:

<?php mail(“root@localhost”, “x”, “x”, “x”, “-H /tmp/rce.sh”); ?>

This is because the fifth parameter of PHP’s mail function allows additional parameters:

By supplying the shell script to the -H parameter. Even though the -H parameter is filtered by escapeshellcmd we can effectively bypass this by supplying a custom script from the disk, which effectively leads to command execution. Finally you can read the contents of the flag via LFI:

/miner/..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252F..%252Ftmp%252Fflag

Final python3 & C exploits:

exploit_fcgi.py

exploit_mail.py

payload_gen.py

FastCGIClient.py

exec.c

--

--