漏洞卡片#
PHPMailer 是 PHP 中用于发邮件的一个库,至今仍为主流,按照官方自己的话说:
Probably the world's most popular code for sending email from PHP!
漏洞影响版本:< 5.2.20
发现者:Dawid Golunski
简述:PHPMailer 5.2.20 之前版本中,由于开发者对 CVE-2016-10033 的修复不当,导致依旧存在老问题。
CWE-88:Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')(命令中参数分隔符未正确中和(“参数注入”))
Base Score:9.8 CRITICAL
Vector:CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
漏洞讲解#
本文默认你已经读过 CVE-2016-10033。
针对 CVE-2016-10033 的修复,主要采用的是添加额外的 escapeshellarg 校验,使得对 Sender 属性的校验大致可写成:
escapeshellcmd(escapeshellarg($this->validateAddress($this->Sender)));(依旧以 Unix/Linux 系统为例)但该防御方法仅仅只是在外层再套个单引号而已,依旧可以用老思路突破。
Payload:
"Foo \' -oQ/tmp/ -X/var/www/html/shell.php "@qq.com这首先通过了 validateAddress() 的校验,随后 escapeshellarg() 将其处理成:
'"Foo \' -oQ/tmp/ -X/var/www/html/shell.php "@qq.com'接着,根据规则,escapeshellcmd 会进行转义操作,得到:
'\"Foo \\' -oQ/tmp/ -X/var/www/html/shell.php \"@qq.com\'等价于:
'\"Foo ' -oQ/tmp/ -X/var/www/html/shell.php \"@qq.com\'这又实现了额外参数的注入。
本质依旧是对转义符 \ 的解析差异导致引号(无论单双)恢复其语义。
Diff#
补丁中,开发者新增了一个 isShellSafe() 方法:
+ protected static function isShellSafe($string)
+ {
+ // Future-proof
+ if (escapeshellcmd($string) !== $string
+ or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
+ ) {
+ return false;
+ }
+
+ $length = strlen($string);
+
+ for ($i = 0; $i < $length; $i++) {
+ $c = $string[$i];
+
+ // All other characters have a special meaning in at least one common shell, including = and +.
+ // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
+ // Note that this does permit non-Latin alphanumeric characters based on the current locale.
+ if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }并将该方法应用到 Sender 属性上:
- $params = sprintf('-f%s', escapeshellarg($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);
+ }isShellSafe() 方法依旧用到了 escapeshellcmd 和 escapeshellarg 这两个函数,但是这次对函数执行的结果进行了校验:
| 函数 | 描述 |
|---|---|
escapeshellcmd($string) !== $string | Sender 经过 escapeshellcmd 进行转义处理之后,结果需要和原先的保持一致。换言之,Sender 中不能出现会被 escapeshellcmd 转义的特殊字符以及“落单”的单双引号。之前的 Payload 均通不过该校验。 |
!in_array(escapeshellarg($string), array("'$string'", "\"$string\"") | Sender 经过 escapeshellarg 处理之后,只能是 "……"(Windows 平台)或者 '……'(Linux 平台)这样的形式。 |
并且,即便通过了上述两个函数的检测,后续还要对 Sender 中的字符进行逐个白名单检验:
if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
return false;
}白名单:
- 数字或者字母(
ctyoe_alnum) - 四个特殊字符(
@_-.)
推测安全研究员的发现经过#
- 补丁发布
- 发现补丁依旧存在的问题
- 漏洞利用
CheckLists#
PHP 库。