PHP Mailer 最新代码执行漏洞

0x0 前言

  PHPMailer作为一个组件角色的存在,还是被广泛使用的。不过最近爆出了两个CVE,其中一个还是国内的一家公司报告的,引起了我的兴趣。

0x1 背景

链接:https://github.com/PHPMailer/PHPMailer/blob/v6.5.0/SECURITY.md

关于一些关键词,我已经标注出来,有了这些,漏洞的位置,修补方式都不用看代码了,官方已经说的很明白了,下面只要debug下就行。

0x2 环境搭建

(1)采用composer快速搭建

composer require phpmailer/phpmailer 6.4.1

这个利用composer的一键化,能够非常容易有序地加载各个模块,也比较全。

目录结构如下:

├── composer.json
├── composer.lock
└── vendor
    ├── autoload.php
    ├── composer
    │   ├── ClassLoader.php
    │   ├── LICENSE
    │   ├── autoload_classmap.php
    │   ├── autoload_namespaces.php
    │   ├── autoload_psr4.php
    │   ├── autoload_real.php
    │   ├── autoload_static.php
    │   └── installed.json
    └── phpmailer
        └── phpmailer
            ├── COMMITMENT
            ├── LICENSE
            ├── README.md
            ├── SECURITY.md
            ├── VERSION
            ├── composer.json
            ├── get_oauth_token.php
            ├── language
            │   └── phpmailer.lang-zh_cn.php
            │   └── ...还有很多其他语言
            ├── phpunit.xml.dist
            └── src
                ├── Exception.php
                ├── OAuth.php
                ├── PHPMailer.php
                ├── POP3.php
                └── SMTP.php

这个加载方式,官方提供了很多例子来使用,新建个index.php即可直接加载。

<?php
//Import PHPMailer classes into the global namespace
//These must be at the top of your script, not inside a function
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

//Load Composer's autoloader
require 'vendor/autoload.php';

//Instantiation and passing `true` enables exceptions
$mail = new PHPMailer(true);

try {
    //Server settings
    $mail->SMTPDebug = SMTP::DEBUG_SERVER;                      //Enable verbose debug output
    $mail->isSMTP();                                            //Send using SMTP
    $mail->Host       = 'smtp.example.com';                     //Set the SMTP server to send through
    $mail->SMTPAuth   = true;                                   //Enable SMTP authentication
    $mail->Username   = 'user@example.com';                     //SMTP username
    $mail->Password   = 'secret';                               //SMTP password
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;         //Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged
    $mail->Port       = 587;                                    //TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` above

    //Recipients
    $mail->setFrom('from@example.com', 'Mailer');
    $mail->addAddress('joe@example.net', 'Joe User');     //Add a recipient
    $mail->addAddress('ellen@example.com');               //Name is optional
    $mail->addReplyTo('info@example.com', 'Information');
    $mail->addCC('cc@example.com');
    $mail->addBCC('bcc@example.com');

    //Attachments
    // $mail->addAttachment('/var/tmp/file.tar.gz');         //Add attachments
    // $mail->addAttachment('/tmp/image.jpg', 'new.jpg');    //Optional name

    //Content
    $mail->isHTML(true);                                  //Set email format to HTML
    $mail->Subject = 'Here is the subject';
    $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';

    $mail->send();
    echo 'Message has been sent';
} catch (Exception $e) {
    echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
}

(2)直接下载源码包,简洁。

wget https://github.com/PHPMailer/PHPMailer/archive/refs/tags/v6.4.1.tar.gz

然后按需手动加载:

<?php
use PHPMailer\PHPMailer\PHPMailer;
require 'src/PHPMailer.php';
$mail = new PHPMailer();
var_dump($mail);
?>

0x3 漏洞分析

0x3.1 CVE-2021-3603

1.漏洞分析

调用validateAddress()函数进行检验时,如果其中可选参数$patternselect可控的话,那么可以造成任意调用函数进行代码执行,如果不可控的话,默认则为php,如果项目中存在一个危险的函数名称为php函数,则可以进行调用该危险函数进行利用。

2.漏洞证明

(1)可控两个参数的情况:

(2)只控address参数, 则需要全局注册一个名称为php的函数:

3.分析过程

这个洞非常简单,我们跟进这个函数

PHPMailer.php line: 1333

可以看到如果$patternselect不为空,就可以直接进行任意函数调用:

if (is_callable($patternselect)) {
            //典型的任意函数调用
            return call_user_func($patternselect, $address);
        }

那么如果不赋值的,默认情况是则会进行赋值:

$patternselect = static::$validator;

而这个值,预先在文件中是个已经定义好的值,其值为"php"。

然后就会优先执行全局注册的php函数,比如我上面那个例子,从而导致命令执行。

4.漏洞修复

还是喜欢在Github的对比:

https://github.com/PHPMailer/PHPMailer/commit/45f3c18dc6a2de1cb1bf49b9b249a9ee36a5f7f3#diff-7f9ff1e59fad629c10da93f0d676eadc5ec86634c576f9852a41e8dba6f8afad

这个可以看到,第一次修复的时候,用了白名单的方式,完全禁止了用户调用自定义的函数,不过最终修复方式还是保留支持使用自定义的验证方式,这个就有点巧了,必须要在代码实现,也就是这个行为是被开发者控制的。

is_callable($patternselect) && !is_string($patternselect)

即需要同时满足这两个条件,从外部进行控制的话,始终会被判断为字符串,是不可能满足的。

5.攻击面

比如常用的setFrom函数,参考之前的CVE-2016-10033的利用面。

0x3.2 CVE-2021-34551

1.漏洞分析

setLanguage()函数中的$lang_path参数如果可控,且不过滤的话,可以通过设置为UNC路径,如果服务器能够解析UNC路径,那么将会把UNC路径上的文件当做PHP文件来包含从而导致代码执行,这个主要影响window下的环境。

2.漏洞证明

漏洞代码:

$mail->setLanguage($langcode = $_GET['code'], $lang_path = $_GET['path']);

参考这个一键来搭建: https://xz.aliyun.com/t/5535#toc-2

docker run -v /root/webdav:/var/lib/dav -e ANONYMOUS_METHODS=GET,OPTIONS,PROPFIND -e LOCATION=/webdav -p 80:80 --rm --name webdav bytemark/webdav

然后直接请求就行:

3.分析过程

PHPMailer.php line:2192行

然后继续看下去,

很明显这里做了个拼接赋值给$lang_file,而且是可以控制开头的,即可以使用任意协议,包括unc协议,然后还有个条件,就需要$langcode不能为'en',要不然进不去文件包含函数。所以说,上面漏洞利用中的文件名拼接是有格式的,要根据你传入的$lang_path$langcode来构造。

4.漏洞修复

这个修复白名单方式,直接解析为文本,正则匹配需要的内容,很安全。

5.攻击面

搜索PHPMailer.php文件中的调用,就只有一处,还是默认的,所以说利用面还是比较狭窄。

0x4 个人看法

对于 CVE-2021-3603,开发者本意是希望程序能够向下执行,然后跳到分支结构的php,却忽略了可能存在全局的函数名称为"php"的函数,同时参数也没做任何的过滤,那么可控就会很危险。

对于CVE-2021-34551,属于较为典型的开发细节没注意产生的一个bug,但是我个人觉得漏洞危害并不大,开发者虽然对协议进行了处理控制,导致没办法利用诸如phar:data:之类的协议,但是可能对UNC了解不是很多,忽略了这个这种可能,导致了漏洞的产生。

0x5 总结

  本文开篇介绍了研究的初衷,然后介绍了漏洞的背景,继而说明了环境搭建的流程,然后对两个CVE漏洞展开了较为详细的分析,最后给出了自己的看法。不难看出,这两类漏洞利用还是较为有限的,需要结合特定场景,但是通过分析和学习这两个漏洞,能为自己进行代码审计的时候积累一些实践经验。

点击收藏 | 2 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖