在PHP应用程序开发中不正当使用mail()函数引发的血案

一、前言

在我们挖掘PHP应用程序漏洞的过程中,我们向著名的Webmail服务提供商Roundcube提交了一个远程命令执行漏洞(CVE-2016-9920)。该漏洞允许攻击者通过利用Roundcube接口发送一个精心构造的电子邮件从而在目标系统上执行任意命令。在我们向厂商提交漏洞,发布了相关的漏洞分析文章后,由于PHP内联函数mail()导致的类似安全问题在其他的PHP应用程序中陆续曝出。在这篇文章中,我们将分析一下这些漏洞的共同点,那些安全补丁仍然存在问题,以及如何安全的使用mail()函数。

二、浅析PHP的mail()函数

PHP自带了一个内联函数mail()用于在PHP应用程序中发送电子邮件。开发者可以通过使用以下五个参数来配置邮件发送。

http://php.net/manual/en/function.mail.php


  1. bool mail( 
  2.     string $to, 
  3.     string $subject, 
  4.     string $message [, 
  5.     string $additional_headers [, 
  6.     string $additional_parameters ]] 

这个函数的前三个参数这里就不细说了,因为这些参数一般情况下不会受到注入攻击的影响。但是,值得关注的一点是,如果$to参数由用户控制控制的话,那么其可以向任意电子邮件地址发送垃圾邮件。

三、邮件头注入

在这篇文章中我们重点分析后两个参数。第四个参数$additional_headers的主要功能是规定额外电子邮件报头。比如From、Reply-To、Cc以及Bcc。由于邮件报头由CRLF换行符\r\n分隔。当用户输入可以控制第四个参数,攻击者可以使用这些字符(\r\n)来增加其他的邮件报头。这种攻击方式称为电子邮件头注入(或短电子邮件注入)。这种攻击可以通过向邮件头注入CC:或BCC:字段造成发送多封垃圾邮件。值得注意的是,某些邮件程序会自动将\n替换为\r\n。

四、为什么没有正确处理mail()函数的第5个参数会引发安全问题

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

  • 配置PHP连接的SMTP服务器的主机名和端口
  • 配置PHP用作邮件传输代理(MTA)的邮件程序文件路径

当PHP配置了第二个选项时,调用mail()函数的将导致执行配置的MTA(邮件传输代理)程序。尽管PHP内部可以调用escapeshellcmd()函数防止恶意用户注入其他的shell命令,mail()函数的第5个参数$additional_parameters允许向MTA(邮件传输代理)中添加新的程序参数。因此,攻击者可以在一些MTA中附加程序标志,启用创建一个用户可控内容的文件。

1. 漏洞演示代码     


  1. mail("myfriend@example.com", "subject", "message", "", "-f" . $_GET['from']); 

在上述代码中存在一个远程命令执行漏洞,这个问题容易被没有安全意识的开发人员忽略。GET参数完全由用户控制,攻击者可以利用该处输入向邮件程序传递其他额外的参数。举例来说,在发送邮件的过程中可以使用-O参数来配置发送邮件的选项,使用-X参数可以指定日志文件的位置。

2. 概念性验证(PoC)


  1. example@example.com -OQueueDirectory=/tmp -X/var/www/html/rce.php 

这个PoC的功能是在Web目录中生成一个PHP
webshell。该文件(rce.php)包含受到PHP代码污染的日志信息。因此,当访问rce.php文件时,攻击者能够在Web服务器上执行任意PHP代码。读者可以在我们的发布的文章和这里找到更多关于如何利用这个漏洞的相关信息。

五、最新相关的安全漏洞

在许多现实世界的应用程序中,有很多由于mail()函数的第五个参数使用不当引发的安全问题。最近发现以下广受关注的PHP应用程序受到此类漏洞的影响(多数漏洞由Dawid Golunski发现)。

对应参考链接:CVE-2016-9920DiscussionCVE-2016-10033CVE-2016-10034CVE-2016-10074CVE-2017-7692

由于一些广泛使用的Web应用程序(如WordpressJoomlaDrupal)部分模块基于以上库开发,所以也会受到该类漏洞的影响。

五、为什么escapeshellarg()函数没有那么安全?

PHP提供了escapeshellcmd()escapeshellarg()函数用来过滤用户的输入,防止恶意攻击者执行其他的系统命令或参数。直观来讲,下面的PHP语句看起来很安全,并且防止了-param1参数的中断:


  1. system(escapeshellcmd("./program -param1 ". escapeshellarg( $_GET['arg'] ))); 

然而,当此程序有其他可利用参数时,那么这行代码就是不安全的。攻击者可以通过注入"foobar' -param2 payload
"来突破-param1参数的限制。当用户的输入经过两个escapeshell*函数的处理,以下字符串将到达system()函数。


  1. ./program -param1 'foobar'\\'' -param2 payload \' 

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

PHP的mail()函数在内部使用escapeshellcmd()函数过滤传入的参数,以防止命令注入攻击。这正是为什么escapeshellarg()函数不会阻止mail()函数的第5个参数的攻击。RoundcubePHPMailer的开发人员率先发布了针对该漏洞的补丁。

六、为什么FILTER_VALIDATE_EMAIL是不安全的?

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


  1. filter_var($email, FILTER_VALIDATE_EMAIL) 

但是,并不是所有可能存在安全问题的字符串都会被该过滤器过滤。它允许使用嵌入双引号的转义的空格。

由于函数底层实现正则表达式的原因,filter_var()没有对输入正确的过滤,导致构造的payload被带入执行。   


  1. 'a."'\ -OQueueDirectory=\%0D<?=eval($_GET[c])?>\ -X/var/www/html/"@a.php 

对于上文给出的url编码输入,filter_var()函数返回true,将该payload识别为有效的邮件格式。

当开发人员使用该函数验证电子邮件格式作为唯一的安全验证措施,此时仍然是可以被攻击者利用的:与我们之前的攻击方式类似,在PHP程序发送邮件时,我们精心构造的恶意“电子邮件地址”会将将PHP webshell生成在Web服务根目录下。


  1. <?=eval($_GET[c])?>\/): No such file or directory 

切记,filter_var()不适合用于对用户输入内容的过滤,因为它对部分字符串的验证是不严格的。

七、如何安全的使用mail()函数

仔细分析应用程序中传入mail()函数的参数,满足以下条件:

1. $to 除非可以预期用户的输入内容,否则不直接使用用户输入

2. $subject 可以安全的使用

3. $message 可以安全的使用

4. $additional_headers 过滤\r、\n字符

5. $additional_parameters 禁止用户输入

事实上,当把用户的输入作为shell指令执行时,没有什么办法可以保证系统的安全性,千万不要去考验你的运气。

如果在开发您的应用程序过程中第5个参数一定要由用户控制,你可以使用电子邮件过滤器(email filter)将用户输入的合法数据限制为最小字符集,即使它违反了RFC合规性。我们建议不要信任任何转义或引用程序,因为据历史资料表示这些功能是存在安全问题的,特别是在不同环境中使用时,可能还会暴露出其他安全隐患。Paul Buonopane研究出了另一种方法去解决这个问题,可以在这里找到。

八、总结

许多PHP应用程序都有向其用户发送电子邮件的功能,例如提醒和通知。虽然电子邮件头注入是众所周知的安全问题,但是当开发人员使用mail()函数时,往往会忽视不正当的使用有可能导致远程命令执行漏洞。在这篇文章中,我们主要分析了mail()函数的第5个参数使用不当可能存在的安全风险,以及如何防范这种问题,防止服务器受到攻击。

作者:童话
来源:51CTO

时间: 2024-09-16 03:30:45

在PHP应用程序开发中不正当使用mail()函数引发的血案的相关文章

android 应用程序开发中,清除缓存的功能怎么做啊?

问题描述 android 应用程序开发中,清除缓存的功能怎么做啊? android 应用程序开发中,清除缓存的功能怎么做啊?清除此应用程序的缓存,怎么做? 解决方案 getCacheDir()能够得到当前项目的缓存地址 在项目中经常会使用到WebView 控件,当加载html 页面时,会在/data/data/应用package 目录下生成database与cache 两个文件夹.请求的url 记录是保存在WebViewCache.db,而url 的内容是保存在WebViewCache 文件夹下

Ajax程序开发中常见问题

Ajax程序开发中常见问题,看下文章或许你就少犯了许多错误了. 1.ajax,action中response返回的xml文档格式错误时,eclipse debug进入不到action中.     2.ajax缓存问题,需要加入xmlHttp.setRequestHeader("If-Modified-Since","0");便可解决.     3.如果不是ajax提交,而设置了PrintWriter out = response.getWriter();则jsp会产

界面-web程序开发中文件的下载

问题描述 web程序开发中文件的下载 在登录得主界面增加下载的链接,可以下载发布到服务器上的文件.使用的框架是spring,Struts2,ibatis. 解决方案 如果你不愿暴露文件存储路径,就用io流的方式,若果无所谓,直接把路径放在超链接上即可. 解决方案二: BufferedInputStream br = new BufferedInputStream(new FileInputStream(f)); byte[] buf = new byte[1024]; int len = 0;

java程序 不能运行-JAVA小程序开发中遇到一个问题

问题描述 JAVA小程序开发中遇到一个问题 Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException at Studentguanli.actionPerformed(Studentguanli.java:111) at javax.swing.AbstractButton.fireActionPerformed(Unknown Source) at javax.swing.AbstractButton$

C/C++程序开发中实现信息隐藏的三种类型_C 语言

无论是模块化设计,还是面向对象设计,还是分层设计,实现子系统内部信息的对外隐藏都是最关键的内在要求.以本人浅显的经验,把信息隐藏按照程度的不同分成(1)不可见不可用(2)可见不可用(3)可见可用. 1 不可见不可用 就是说模块内部的变量.结构体.类定义对外部而已完全隐藏,外部对此一无所知.常用的实现方法就是利用不透明指针,请参见我的博文C语言开发函数库时利用不透明指针对外隐藏结构体细节. 这种方法同样适用于C++语言,一种可能的实现方式为面向接口编程. 头文件 IMyClass.h class

Android程序开发中单选按钮(RadioGroup)的使用详解

在还没给大家介绍单选按钮(RadioGroup)的使用,先给大家展示下效果图吧: xml文件 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_heig

iOS程序开发中设置UITableView的全屏分隔线的方法(不画线)_IOS

ableView是app开发中常用到的控件,功能很强大,多用于数据的显示.下面给大家介绍设置UITableView的全屏分隔线的两种方法. 具体详情如下所示: 如图 添加如下代码 sTableView.separatorInset = UIEdgeInsetsZero; sTableView.layoutMargins = UIEdgeInsetsZero; cell.layoutMargins = UIEdgeInsetsZero; 第二种方法如下图 -(void)viewDidLayoutS

Ubuntu中启用php的mail()函数并解决发送邮件速度慢问题

 本文主要给大家介绍的是在Ubuntu下安装sendmail的方法,以及启用sendmail之后,php发送邮件缓慢的原因及解决方法,有需要的小伙伴可以参考下.     如果需要用php的mail()函数来发送邮件,是需要服务器安装sendmail组件才能支持的,这个在php的手册中mail()函数部分也有介绍到.然后在 在Ubuntu下安装sendmail的命令:   代码如下: sudo apt-get install sendmail   安装好之后,启动sendmail服务:   代码如

Ubuntu中启用php的mail()函数并解决发送邮件速度慢问题_php实例

如果需要用php的mail()函数来发送邮件,是需要服务器安装sendmail组件才能支持的,这个在php的手册中mail()函数部分也有介绍到.然后在 在Ubuntu下安装sendmail的命令: 复制代码 代码如下: sudo apt-get install sendmail 安装好之后,启动sendmail服务: 复制代码 代码如下: sudo service sendmail start 有了sendmail的支持,就可以在php中用mail()函数发送邮件了. 一般造成在php用mai