CVE-2016-10045

PHPMailer 5.2.20 之前版本中,由于开发者对 CVE-2016-10033 的修复不当,导致依旧存在老问题。

漏洞卡片#

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

漏洞讲解#

Tip

本文默认你已经读过 CVE-2016-10033。

针对 CVE-2016-10033 的修复,主要采用的是添加额外的 escapeshellarg 校验,使得对 Sender 属性的校验大致可写成:

PHP
escapeshellcmd(escapeshellarg($this->validateAddress($this->Sender)));

(依旧以 Unix/Linux 系统为例)但该防御方法仅仅只是在外层再套个单引号而已,依旧可以用老思路突破。

Payload:

Text
"Foo \' -oQ/tmp/ -X/var/www/html/shell.php "@qq.com

这首先通过了 validateAddress() 的校验,随后 escapeshellarg() 将其处理成:

Text
'"Foo \' -oQ/tmp/ -X/var/www/html/shell.php "@qq.com'

接着,根据规则,escapeshellcmd 会进行转义操作,得到:

Text
'\"Foo \\' -oQ/tmp/ -X/var/www/html/shell.php \"@qq.com\'

等价于:

Text
'\"Foo ' -oQ/tmp/ -X/var/www/html/shell.php \"@qq.com\'

这又实现了额外参数的注入。

本质依旧是对转义符 \ 的解析差异导致引号(无论单双)恢复其语义。

Diff#

补丁中,开发者新增了一个 isShellSafe() 方法:

Diff
+ 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 属性上:

Diff
- $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) !== $stringSender 经过 escapeshellcmd 进行转义处理之后,结果需要和原先的保持一致。换言之,Sender 中不能出现会被 escapeshellcmd 转义的特殊字符以及“落单”的单双引号。之前的 Payload 均通不过该校验。
!in_array(escapeshellarg($string), array("'$string'", "\"$string\"")Sender 经过 escapeshellarg 处理之后,只能是 "……"(Windows 平台)或者 '……'(Linux 平台)这样的形式。

并且,即便通过了上述两个函数的检测,后续还要对 Sender 中的字符进行逐个白名单检验:

PHP
if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
	return false;
}

白名单:

  • 数字或者字母(ctyoe_alnum)
  • 四个特殊字符(@_-.)

推测安全研究员的发现经过#

  1. 补丁发布
  2. 发现补丁依旧存在的问题
  3. 漏洞利用

CheckLists#

PHP 库。

参考文章#

键盘快捷键

全站

CtrlK
搜索
/
搜索
?
显示快捷键
Esc
关闭弹窗、退出模式

阅读文章

T
目录
F
专注模式
A
批注模式

批注模式

P
笔
H
荧光笔
L
激光笔
E
橡皮擦
1–6
切换颜色
Shift
按住拖动画直线
CtrlZ
撤销
CtrlShiftZ
重做