CVE-2017-5223-PHPMailer任意文件读取漏洞复现

CVE-2017-5223-PHPMailer任意文件读取漏洞复现

影响版本: PHPMailer <= 5.2.21

漏洞原理

文件读取的函数主要是 encodeFile函数。(贴关键代码)

    protected function encodeFile($path, $encoding = 'base64')
    {
        ......
            $file_buffer = file_get_contents($path);
            $file_buffer = $this->encodeString($file_buffer, $encoding);
      
       .......
            return $file_buffer;
        } catch (Exception $exc) {
            $this->setError($exc->getMessage());
            return '';
        }
    }

该函数中接收了一个$path变量,最后该$path变量的值带入到了file_get_contents函数中执行。如果该$path变量可控即可任意文件读取.

$path参数回溯

在attachAll函数中:

// Add all attachments
foreach ($this->attachment as $attachment) {
    // Check if it is a valid disposition_filter
    if ($attachment[6] == $disposition_type) {
        // Check for string attachment
        $string = '';
        $path = '';
        $bString = $attachment[5];
        if ($bString) {
            $string = $attachment[0];
        } else {
            $path = $attachment[0];
        }

可以看到$path的赋值过程,只要是$this->attachment数组中每一个attachment的第六个元素为False,就将$attachment[0] 赋值给 $path.

跟进attachment数组

public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
{
...
            $this->attachment[] = array(
            0 => $path,
            1 => $filename,
            2 => $name,
            3 => $encoding,
            4 => $type,
            5 => false, // isStringAttachment
            6 => $disposition,
            7 => 0
        );
        

public function addStringAttachment(
    $string,
    $filename,
    $encoding = 'base64',
    $type = '',
    $disposition = 'attachment'
) {
    // If a MIME type is not specified, try to work it out from the file name
    if ($type == '') {
        $type = self::filenameToType($filename);
    }
    // Append to $attachment array
    $this->attachment[] = array(
        0 => $string,
        1 => $filename,
        2 => basename($filename),
        3 => $encoding,
        4 => $type,
        5 => true, // isStringAttachment
        6 => $disposition,
        7 => 0
    );
}


public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
{
    if (!@is_file($path)) {
        $this->setError($this->lang('file_access') . $path);
        return false;
    }

    // If a MIME type is not specified, try to work it out from the file name
    if ($type == '') {
        $type = self::filenameToType($path);
    }

    $filename = basename($path);
    if ($name == '') {
        $name = $filename;
    }

    // Append to $attachment array
    $this->attachment[] = array(
        0 => $path,
        1 => $filename,
        2 => $name,
        3 => $encoding,
        4 => $type,
        5 => false, // isStringAttachment
        6 => $disposition,
        7 => $cid
    );
    return true;
}

    public function addStringEmbeddedImage(...){
        $this->attachment[] = array(
            0 => $string,
            1 => $name,
            2 => $name,
            3 => $encoding,
            4 => $type,
            5 => true, // isStringAttachment
            6 => $disposition,
            7 => $cid
        );        
    }

只有addAttachment()函数和addEmbeddedImage() 函数可以,其他两个函数$attachment[5] 为true.

主要看AddEmbeddedImage函数,该函数是处理邮件内容中的图片的,回溯该函数发现msgHTML函数调用了该函数,msgHTML 函数是用来发送html格式的邮件。

   public function msgHTML($message, $basedir = '', $advanced = false)
    {
        preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
        if (array_key_exists(2, $images)) {
            foreach ($images[2] as $imgindex => $url) {
                // Convert data URIs into embedded images
                if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
                    $data = substr($url, strpos($url, ','));
                    if ($match[2]) {
                        $data = base64_decode($data);
                    } else {
                        $data = rawurldecode($data);
                    }
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
                    if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
                        $message = str_replace(
                            $images[0][$imgindex],
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
                            $message
                        );
                    }
                } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) {
                    // Do not change urls for absolute images (thanks to corvuscorax)
                    // Do not change urls that are already inline images
                    $filename = basename($url);
                    $directory = dirname($url);
                    if ($directory == '.') {
                        $directory = '';
                    }
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
                    if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
                        $basedir .= '/';
                    }
                    if (strlen($directory) > 1 && substr($directory, -1) != '/') {
                        $directory .= '/';
                    }
                    if ($this->addEmbeddedImage(
                        $basedir . $directory . $filename,
                        $cid,
                        $filename,
                        'base64',
                        self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
                    )

$url是通过解析$message里src=”xxxxx”而来的,$url最终被解析出来就是xxxxx,而$message就是我们发送邮件的自定义的内容。这样可控点就找到了,即可成功利用该漏洞了

查找所有触发encodeFile 函数的触发链。

我们直接查看一下encodeFile的所有引用,只有class.phpmailer.php中的attachAll函数有调用。

发现也只有同文件的createBody()函数调用了,而且是多次调用。

继续往上回溯,我们发现调用链为;

send()->preSend()->createBody->attachAll()->encodeFile()

我们找到一处触发链,找到两个函数可以添加我们的可控变量$path参数。

复现

使用vulhub环境即可,具体操作详见

https://vulhub.org/#/environments/phpmailer/CVE-2017-5223/

“意见反馈”页面,正常用户填写昵称、邮箱、意见提交,这些信息将被后端储存,同时后端会发送一封邮件提示用户意见填写完成:

该场景在实战中很常见,比如用户注册网站成功后,通常会收到一封包含自己昵称的通知邮件,那么,我们在昵称中插入恶意代码`<img src="/etc/passwd">`,目标服务器上的文件将以附件的形式被读取出来。
image-20220105163935385

总结

代码审计步骤仍然是找到危险函数,然后变量回溯,寻找可控变量,利用漏洞。

参考

https://github.com/jiangsir404/PHP-code-audit/blob/master/PHPmailer/PHPmailer 任意文件读取漏洞.md

https://vulhub.org/#/environments/phpmailer/CVE-2017-5223/

posted @ 2022-02-06 17:22  kzd的前沿思考  阅读(338)  评论(0编辑  收藏  举报