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 = '[email protected]'; //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('[email protected]', 'Mailer');
$mail->addAddress('[email protected]', 'Joe User'); //Add a recipient
$mail->addAddress('[email protected]'); //Name is optional
$mail->addReplyTo('[email protected]', 'Information');
$mail->addCC('[email protected]');
$mail->addBCC('[email protected]');
//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
这个可以看到,第一次修复的时候,用了白名单的方式,完全禁止了用户调用自定义的函数,不过最终修复方式还是保留支持使用自定义的验证方式,这个就有点巧了,必须要在代码实现,也就是这个行为是被开发者控制的。
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
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漏洞展开了较为详细的分析,最后给出了自己的看法。不难看出,这两类漏洞利用还是较为有限的,需要结合特定场景,但是通过分析和学习这两个漏洞,能为自己进行代码审计的时候积累一些实践经验。
BY:先知论坛
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论