为什么mail()函数在PHP中是危险的

为什么mail()在PHP中是危险的

0x001 前言

在出现PHP应用程序漏洞的时候,我们报道了Webmailer Roundcube应用CVE-2016-9920 中的远程命令执行漏洞。此漏洞允许恶意用户通过Roundcube接口写入电子邮件,在目标服务器上执行任意系统命令。在我们向供应商报告漏洞并发布了博客之后,类似的基于PHP内置mail()函数的安全漏洞也在其他PHP应用中出现。在本篇文章中,我们将看看这些漏洞的共同点,那些安全补丁有问题,以及如何安全的使用mail()

0x002 PHP mail() 函数

mail()用于从PHP应用程序发送电子邮件的内置功能。可以通过使用以下五个参数来配置邮件传递。
http://php.net/manual/en/function.mail.php

该功能的前三个参数是不言而喻的,较不敏感,因为这些参数不受注入攻击的影响。但是,请注意,如果to参数可以由用户控制,那么可以将垃圾邮件发送到任意地址。

0x003 电子邮件标题注入

最后两个可选参数更使人关注。第四个参数$additional_headers接收附加到电子邮件头到字符串。在这里,可以指定额外的电子邮件标题,例如From:Reply-To。邮件邮件头由CRLF换行符\r\n分割,所有当用户输入在第四个参数中被无符号化时,攻击者可以使用这些字符附加额外的电子邮件标题。这种攻击称为电子邮件头部注入(或者短电子邮件注入)。通过向CC:BCC:注入标题添加多个电子邮件地址,可以发送多个垃圾邮件。请注意,有些邮件程序会替换\n \r\n

0x004 为什么mail()的第五个参数是危险的

为了使用PHP 中的mail() 函数,必须配置电子邮件程序或服务器。在php.ini配置文件中可以使用以下两个选项:

  1. 配置SMTP服务器的主机名和端口
  2. 配置PHP用作邮件传输代理(MTA)的文件路径

PHP配置了第二个选项时,对该mail()函数对调用将导致执行配置对MTA程序。虽然PHP内部使用escapeshellcmd()用于程序调用,防止新的shell命令注入,第5个参数$additional_parametersmail()允许添加的新程序。因此,攻击者可以附加程序标志,在某些MTA中可以创建具有用户控制内容的文件。

Vulnerable Code

上面显示的代码容易被远程执行,容易被忽略。GET参数来自于使用unsanitized,并允许攻击者将其他参数传递给邮件程序。例如,在sendmail中,该参数-O可用于重新配置sendmail选项,该参数-X指定日志文件的位置。

概念证明

概念证明将在应用程序的Web目录中放置一个PHP shell。该文件包含可能受到PHP代码污染的日志信息。因此,当访问rce.php文件时,攻击者能够在Web服务器上执行任意PHP代码。您可以在我们的博文和这里找到更多关于如何利用这个问题的信息。

0x005 最新相关的安全漏洞

在许多现实世界的应用中,第五个参数确实以易受攻击的方式使用。最近发现以下受欢迎的PHP应用程序受到影响,所有这些都是以前所述的安全问题(主要由Dawid Golunski报道)。

应用 版本 参考
Roundcube <= 1.2.2 CVE-2016-9920
MediaWiki < 1.29 Discussion
PHPMailer <= 5.2.18 CVE-2016-10033
ZendFramework < 2.4.11 CVE-2016-10034
SwiftMailer <= 5.4.5-DEV CVE-2016-10074
SquirrelMail <= 1.4.23 CVE-2017-7692
WordPress 4.6 CVE-2016-10033

其他广泛使用的应用程序,如WordPressJoomlaDrupal也受到影响。

0x006 为什么escapeshell()不安全

PHP提供escapeshellcmd()escapeshellarg()来保护系统命令或参数中使用的用户输入。直观地说,以下PHP语句看起来很安全,并且阻止了该-param1参数:

然而,对于所有本能,当程序具有其他可利用参数时,这种说法是不安全的。攻击者可以-param1通过注入来突破参数“foobar’ -param2 payload “。在这两个功能escapeshell处理完这个输入后,下面的字符串就会到达这个system()*功能。

从执行的命令可以看出,两个嵌套的转义函数混淆了引用并允许附加另一个参数param2

PHP的功能mail()内部使用这个escapeshellcmd()功能来防止命令注入攻击。这正是为什么escapeshellarg()不使用第5个参数来防止攻击mail()RoundcubePHPMailer的,在第一次实施这种错误的补丁。

0x007 为什么FILTER_VALIDATE_EMAIL不安全

另一种直观的方法是使用PHP的电子邮件过滤器,以确保在第5个参数中只使用有效的电子邮件地址mail()

但是,并非所有使用mail()所必需的字符都被该被过滤器禁止。它允许使用嵌入双引号的转义。由于底层正则表达式的性质,有可能重叠单引号和双引号,并将其filter_var()转化为双引号内,虽然mail()内部escapeshellcmd()认为我们不是。

对于这里给定的url编码输入,该filter_var()函数返回true并将有效负载评估为有效的电子邮件地址。当使用此功能作为唯一的安全措施时,这具有重要的影响:类似于我们的原始攻击,我们的恶意“电子邮件地址”将导致sendmail “@a.php在我们的webroot中的新生成的shell,并打印以下错误。

记住,filter_var()不适合用于用户输入的过滤,并且从未针对这种情况,因为它对于几个字符太松散。

0x008 如何安全地使用mail()

仔细分析你的应用程序中每个调用mail()的参数有以下条件:

  1. 参数(to):除非有意义,否则不直接使用用户输入
  2. 参数(subject):使用安全
  3. 参数(message):安全使用
  4. 参数(headers):全部\r和\n字符被剥离
  5. 参数(parameters):不使用用户输入

事实上,没有保证安全的方式使用用户提供的数据。如果你的应用程序在第五个参数中需要用户输入,则可以应用限制性电子邮件过滤器,将任何输入限制为最小字符集,即使它违反了RFC合规性。我们建议不要信任任何转义或引用程序,因为历史已经显示这些功能可以或将被破坏,特别是在不同环境中使用时。保罗·布诺帕内(Paul Buonopane)开发了另一种方法,可以在这里找到。(https://gist.github.com/Zenexer/40d02da5e07f151adeaeeaa11af9ab36

0x009 概要

许多PHP应用程序向其用户发送电子邮件,例如提醒和通知。虽然电子邮件标题注入是众所周知的,但在使用时很少考虑远程命令执行漏洞mail()。在这篇文章中,我们强调了第五个参数的风险mail()以及如何防范可能导致完全服务器受损的攻击。确保您的应用程序安全地使用此内置功能!

参考

https://phabricator.wikimedia.org/T152717
https://framework.zend.com/security/advisory/ZF2016-04
http://seclists.org/fulldisclosure/2017/Apr/86
https://packetstormsecurity.com/files/140290/swiftmailer-exec.txt
http://www.ietf.org/rfc/rfc822.txt