0x00 漏洞概述

WordPress 4.6 版本远程代码执行漏洞是一个非常严重的漏洞,未经授权的攻击者利用该漏洞就能实现远程代码执行,针对目标服务器实现即时访问,最终导致目标应用服务器的完全陷落。无需插件或者非标准设置,就能利用该漏洞。Dawid Golunski (@dawid_golunski) 还在poc中为我们展示了精彩的替换 / 和 “ ”(表示空格)的技巧。

0x01 漏洞分析

整个过程利用了 WordPress 未对请求的 Host 字段进行校验和 PHPMailer 在 小于 5.2.20 版本存在的代码执行漏洞。对以上两个不安全点进行利用,导致远程代码执行。

POC地址为 WordPress-Exploit-4-6-RCE-CODE-EXEC-CVE-2016-10033

我们测试的命令为

/usr/bin/touch /tmp/manning.test

我们看下POC发出的请求

Host 字段构造如下

Host: target(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}usr${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}touch${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}manning.test}} null)

这点跟我们认知中的Host完全不一样。

接下来的流程非常简单。

在 wp-login.php 中,首先根据请求中的 action 进行路由

接着进入函数 retrieve_password

接着进入 wp_mail 函数,位于文件 pluggable.php

在 wp_mail 中,WordPress 会把 _SERVER[‘SERVER_NAME’] 变量拼接到 from_email 变量中。

经过一系列的邮件内容拼接,把类对象 phpmailer 的类变量都进行了赋值,之后进入调用了 Send 函数

这里把最关键的 mailSend 函数贴出来,mailSend 函数负责最终调用,关键在 mailPassthru 函数,该函数会把带有恶意的 params 变量交给 PHPMailer。

protected function mailSend($header, $body)
{
    $toArr = array();
    foreach ($this->to as $toaddr) {
        $toArr[] = $this->addrFormat($toaddr);
    }
    $to = implode(', ', $toArr);

    if (empty($this->Sender)) {
        $params = ' ';
    } else {
        $params = sprintf('-f%s', $this->Sender);
    }
    if ($this->Sender != '' and !ini_get('safe_mode')) {
        $old_from = ini_get('sendmail_from');
        ini_set('sendmail_from', $this->Sender);
    }
    $result = false;
    if ($this->SingleTo && count($toArr) > 1) {
        foreach ($toArr as $toAddr) {
            $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
            $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
        }
    } else {
        $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
        $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
    }
    if (isset($old_from)) {
        ini_set('sendmail_from', $old_from);
    }
    if (!$result) {
        throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
    }
    return true;
}

动态调试,在 mailPassthru 调用时,整个变量如图所示。

最终在 mailPassthru 内调用了 @mail。

最终我们在 tmp 目录看到了 manning.test 文件

0x02 补丁分析

WordPress 4.7.1 版本

1,升级phpmailer的版本到5.2.22

2,在 mailSend 修改,对变量 params 进行了过滤。

protected function mailSend($header, $body)
{
    $toArr = array();
    foreach ($this->to as $toaddr) {
        $toArr[] = $this->addrFormat($toaddr);
    }
    $to = implode(', ', $toArr);

    $params = null;
    //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
    if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
        if (self::isShellSafe($this->Sender)) {
            $params = sprintf('-f%s', $this->Sender);
        }
    }
    if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
        $old_from = ini_get('sendmail_from');
        ini_set('sendmail_from', $this->Sender);
    }
    $result = false;
    if ($this->SingleTo and count($toArr) > 1) {
        foreach ($toArr as $toAddr) {
            $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
            $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
        }
    } else {
        $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
        $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
    }
    if (isset($old_from)) {
        ini_set('sendmail_from', $old_from);
    }
    if (!$result) {
        throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
    }
    return true;
}

0x03 防护建议

升级 WordPress 至最新版本。

0x04 调试总结

调试过程中需要注意:

  • sendmail 需要装 exim4扩展
  • 需要更改poc中的账户,poc中填写的是admin
  • poc运行环境需要有python环境

本次调试环境是:

  • Server version: Apache/2.4.18 (Ubuntu)
  • PHP 7.0.15-0ubuntu0.16.04.4 (cli) ( NTS )
  • sendmail 和 exim4扩展

其他注意事项可以参考 @Tomato菜的要死 和 @廖新喜1 的微博。

0x05 参考文章