4.9 发送邮件的问题
Web 应用通常使用邮件的方式来向用户进行确认或发送通知。而如果邮件发送功能不完善,就可能会导致开放转发第三方邮件,或者邮件内容被篡改等问题。本节就将讲述邮件发送功能中产生的安全隐患。
4.9.1 发送邮件的问题概要
与发送邮件相关的问题有如下三项。
- 邮件头注入漏洞
- 使用 hidden 参数保存收件人信息
- 邮件服务器的开放转发(参考)
- 邮件头注入漏洞
邮件头注入是指,通过在邮件消息中的收件人或标题等字段中插入换行符,从而添加新的邮件头字段或篡改邮件正文的攻击手段。招致此类攻击的漏洞即称为邮件头注入漏洞。
邮件头注入漏洞将于 4.9.2 节详细讲述。
- 使用 hidden 参数保存收件人信息
在一些用来免费发送邮件的表单中,为了便于自定义,有时会将邮件的收件人等信息指定为 hidden 参数(图 4-81)。
图 4-81 将收件人保存在 hidden 参数中的表单
通过将 hidden 参数中的收件人更改为任意的邮箱地址,此类表单就能够被用于发送垃圾邮件。因此,收件人邮箱地址等信息不应该被保存在 hidden 参数中,而是应该硬编码在源代码中,或者被保存在服务器上的安全场所(如文件或数据库等)。
- 参考:邮件服务器的开放转发
邮件服务器(Mail Transfer Agent,MTA)的设置如果存在问题,就可能使服务器的角色既非发件人也非收件人,而是被用于转发第三方的邮件(开放转发)。这样的服务器通常会沦落为发送垃圾邮件的工具。然而由于导致这种问题的原因并不是应用程序方面的问题,因此这里只将其作为参考内容介绍给读者。
图 4-82 转发垃圾邮件的情形
图 4-82 展示了恶意发送垃圾邮件的情形。图中右侧的服务器(A)由于收到过左侧服务器(X)发过来的垃圾邮件,因此便进行了设置,拒绝接收 X 服务器发来的邮件。而垃圾邮件的发送方随后发现了允许开放转发的邮件服务器(R),于是就利用该服务器发送垃圾邮件。由于经过 R 服务器的邮件并没有被服务器 A 拒绝接收,因此服务器 A 就依然能收到 X 发送的垃圾邮件。
针对以上问题,现在的邮件服务器软件(MTA)中都默认不允许开放转发,因此只要正确设置邮件服务器就不会出现问题。网络上也有能够检查邮件服务器是否为开放转发的网站,设置完邮件服务器后可以去这些网站确认一下。
4.9.2 邮件头注入漏洞
概要
邮件头注入为,当收件人(To)或标题(Subject)等邮件头由外部指定时,通过使用换行符来添加或更改邮件头或正文的手段。
邮件头注入漏洞的影响如下。
- 标题、发件人或正文被更改
- 被用来发送垃圾邮件
- 被用来发送病毒邮件
防范邮件头注入漏洞的方法为,使用专门用来发送邮件的程序库,并执行以下任一操作。
- 邮件头中不允许包含外界传入的参数
- 通过校验不允许外界传入的参数中包含换行符
邮件头注入漏洞总览
攻击手段与影响
下面我们就来看一下邮件头注入攻击的方法及其造成的影响。
以下为用来发送邮件的表单。
代码清单 /49/49-001.html
<body>
咨询发送表单 <br>
<form action="49-002.php" method="POST">
邮箱地址 :<input type="text" name="from"><br>
正文 :<textarea name="body">
</textarea>
<input type="submit" value=" 发送 ">
</form>
</body>
然后,使用以下脚本接收表单的值并执行发送邮件的处理。
代码清单 /49/49-002.php
<?php
$from = $_POST['from'];
$body = $_POST['body'];
mb_language('Japanese');
mb_send_mail("wasbook@example.jp", " 收到咨询信件 ",
" 收到了以下用户发来的咨询,请进行处理。\n\n" . $body,
"From: " . $from);
?>
<body>
邮件发送成功
</body>
mb_send_mail
为支持多字节字符的邮件发送函数,各个参数分别为:收件人地址、标题、正文、附加邮件头。上述脚本中使用了第 4 个参数(附加邮件头)指定发件人(From)地址。
关于第 4 个参数,官方文档 50 中有如下记载。
50 http://php.net/manual/zh/function.mb-send-mail.php
additional_headers 被插入在邮件头的末尾。常用于增加额外的头。通过使用换行符("\n")进行分隔,可以指定多个头。
由此可见,虽然利用换行符就能够指定多个邮件头,但是上述应用中却没有考虑到存在换行符的可能性。而这也是形成安全隐患的直接原因,详情会在之后进行讲述。
首先我们来看正常情况下的使用实例。在表单的邮箱地址处填入“alice@example.jp”,在正文处填入“请问订单编号为 4309 的交货期限是什么时候”,然后点击发送按钮,邮件就会被发送,如下图所示。
图 4-83 邮件发送表单
图 4-84 通过表单发送的邮件(正常情况)
这里的收件人 wasbook 就相当于处理用户咨询的客服人员。
接下来,我们就来看一下如何针对此表单实施攻击。
- 攻击方式 1:添加收件人
邮件头注入攻击的第一种方式就是添加收件人。首先我们准备了攻击使用的表单 49-900.html。此表单与 49-001.html 大致相同,只是将邮箱地址输入框改成了能够输入换行符的 textarea 要素,另外,由于假设该网页要被托管在攻击者的网站上,因此这里将 form 元素的 action 属性改成了 URL 的绝对地址。两者的差异如下所示,阴影部分即为不同点。
代码清单 /49/49-900.html(与 49-001.html 的差异)
【略】 <form action="http://example.jp/49/ 49-002.php" method="POST"> 邮箱地址 :<textarea name="from" rows="4" cols="30"> </textarea> <br> 【略】
打开该表单所在的网页,然后输入如下图所示的值。
图 4-85 通过攻击使用的表单发送邮件
点击页面上的发送按钮后,邮件就会被发送至 bob。以下是 Becky !中收到邮件的界面。
图 4-86 除了客服(wasbook)以外 bob 也收到了邮件
虽然客服(wasbook)也收到了同样的邮件,但由于添加 bob 时使用了 Bcc(密送)的方式,因此客服并不知道该邮件也被发送给了 bob,而且很可能只是认为收到了垃圾邮件而立刻将其删除。
除了 Bcc,49-002.php 中还能够添加 Cc 或 To(收件人)、Reply-To 等。同样也能添加 Subject(标题),但添加标题后邮件头中就有了两个 Subject,究竟显示哪一个则要取决于所使用的邮件客户端。
- 攻击方式 2:篡改正文
在上面的攻击方式中,正文中还保留了“收到了以下用户发来的咨询……”的信息,因此并没有达到任意更改正文内容的效果。下面我们就使用邮件头注入攻击来尝试更改正文。其实更改正文的方法很简单,只需在邮箱输入框的 From 地址中插入一个空行就能够书写邮件的正文。假设在 49-900.html 的邮箱输入框内输入以下内容。由于使用中文的话需要一些 MIME 的知识,因此此处的例子中我们采用了英语。
trap@trap.example.com Bcc: bob@example.jp Super discount PCs 80% OFF! http://trap.example.com/
点击发送按钮后,邮件客户端(Foxmail)中就会显示如下。
图 4-87 在邮箱输入框中输入的内容出现在了正文中
可以看到添加在 From 字段之后的消息出现在了正文中。
然而这样的邮件正文会让人感觉很可疑,因此在实际的攻击中,攻击者会使用大量的空行来迷惑用户,或者使用 MIME 来隐藏后面的正文消息。另外还能够添加附件。下面我们就来简单地介绍一下这种方法。
- 通过邮件头注入攻击添加附件
上面说到通过邮件头注入攻击还能够添加附件。下图即为使用 49-002.php 将恶意软件(实际为防病毒软件的测试用文件 51 )以附件的形式添加到邮件中的结果。可以看出,正文中使用了中文,原来的正文则被很好地隐藏了起来。
图 4-88 通过邮件头注入攻击能够添加附件
攻击的奥秘在于恶意利用了 MIME 的 multipart/mixed 形式。读者们只需在安全隐患试验环境中的 49-901.html 中点击发送按钮就能够亲自体验这一攻击手段。此外,在 http://example.jp/49 的菜单中选择“5. 49-901: 咨询表单(通过邮件头注入攻击添加附件)”,也能够轻松地打开该页面。
但是,如果计算机中安装了防病毒软件,附件就有可能被删除或被替换为别的文件。上面的截图是暂时关闭了防病毒软件后取得的。
51 详情请参考 7.4.4 节。
安全隐患的产生原因
要理解邮件头注入漏洞产生的原因,就必须要知道邮件的消息格式。邮件的消息格式与 HTTP 相似,消息头与正文用空行相隔。图 4-89 即为邮件消息的示例。
消息头52 | To: wasbook@example.jp Subject: =?ISO-2022-JP?B?GyRCTGQkJDlnJG8kOyQsJCIbKEI=?= =?ISO-2022-JP?B?GyRCJGokXiQ3JD8bKEI=?= From: alice@example.jp Content-Type: text/plain; charset=ISO-2022-JP |
空行 | |
正文 | 收到了以下用户发来的咨询,请进行处理 请问发货编号为 4309 的交货期限是什么时候 |
图 4-89 邮件的消息格式
52 Subject 消息头占了 2 行是因为使用了“续行”。续行的第 2 行以后的行以空格开头。收件人的邮箱地址很长的时候也会用到续行。其实 HTTP 中也定义了续行,但平时几乎不会使用。
To 为收件人,Subject 为标题,From 为发件人的邮箱地址。发送邮件时经常使用的 sendmail 命令以及多数发送邮件程序库都会从邮件的消息头中取得发送目标的邮箱地址 53 。
53 sendmail 命令在默认情况下会以命令参数的形式来指定收件人。而指定了 -t 选项后,收件人的邮箱地址就可以从邮件消息的 To、Cc、Bcc 中取得。
邮件头注入漏洞产生的主要原因与 HTTP 消息头注入漏洞相似。我们知道,消息头中各字段以换行符隔开,因此,如果能够在外界传入的参数中插入换行符,那么就可以添加新的消息头。下图为在 From 消息头后添加 Bcc 消息头的例子。
图 4-90 添加 Bcc 消息头
同样,使用该方法也能够添加正文。
图 4-91 添加正文
由此可见,换行符在邮件的消息头中有着特殊的意义,如果应用中没有对换行符做相应的处理,就会给外界以添加或更改消息头和正文的可乘之机。而这也是邮件头注入漏洞产生的原因。尤其是在 CGI 程序中发送邮件时,以前普遍采用自己生成邮件消息并使用 sendmail 命令发送的方法,然而使用这种方法生成邮件消息时是极易被混入安全隐患的。
对策
为了消除邮件头注入隐患,首先就要停止使用 sendmail 命令来发送邮件,而是使用专门的程序库。
- 使用专门的程序库来发送邮件
在此基础上,推荐再配合采用以下任一方法。
- 不将外界传入的参数包含在邮件头中
- 发送邮件时确保外界传入的参数中不包含换行符
下面我们就来依次讲解上述对策。
- 使用专门的程序库来发送邮件
发送邮件时,相比于自己生成邮件消息,使用专门的程序库更为安全。使用程序库有以下 3 个优点。
- 使用 sendmail 命令发送邮件时,邮件消息的生成全部由应用程序方面负责,容易引入漏洞
- 调用 sendmail 命令时容易混入 OS 命令注入漏洞(参考 4.11 节)
- 理论上专门的程序库中已经做好了邮件头注入漏洞的防范策略
但是,由于不少专门用于发送邮件的程序库中也被曝出了邮件头注入漏洞 54 ,因此,除了使用专门的程序库之外,还需要配合执行先前列出的两个对策中的任意一个。
- 不将外界传入的参数包含在邮件头中
只要确保邮件头中不包含外界传入的参数,就能够彻底杜绝邮件头注入漏洞。因此,在应对邮件头注入漏洞时,应该首先考虑这一措施。
比如在 49-002.php 中,用户输入的邮箱地址被设置为了 From 邮件头,但由于该邮件的发送目标是客服管理员,因此,将 From 消息头固定并在正文中显示用户的邮箱地址,也同样能够达到此表单的目的。
由此可见,如果可能的话,最好不要在邮件头中包含外界传入的参数。
- 发送邮件时确保外界传入的参数中不包含换行符
如果邮箱地址或标题等允许包含换行符,那么就可能会被添加新的邮件头或正文,从而导致邮件头注入漏洞的产生。由于邮箱地址或标题中本身就不允许包含换行符,所以只需在发送邮件时对换行符进行校验,就可以从根本上防范邮件头注入漏洞。
具体方法为,不直接调用
mb_send_mail
这类发送邮件时使用的程序库函数,而是编写专门用于发送邮件的包装函数 55 ,并在包装函数中校验换行符。另外,在框架提供的发送邮件功能中嵌入校验换行符的处理也是有效的。 - 邮件头注入的辅助性对策
正如前面所说的那样,邮件头中设置的邮箱地址和标题中本来就不应该包含换行符,而这也应该被包含在输入值校验的范围之内。因此,只要进行了妥善的输入校验,就会有助于防范邮件头注入漏洞。
- 校验邮箱地址
虽然 RFC532256 中规定了邮箱地址的格式,但是 RFC 中的规定相当复杂,并非所有的邮件服务器、邮件客户端和 Web 邮箱服务都完全支持 RFC 中的规定。因此,只要在各个项目需求中确定邮箱地址的格式,然后再在程序的输入校验中检查是否符合该格式即可。
- 校验标题
由于标题(Subject 消息头)中没有格式和字符种类的限制,因此只要使用 4.2 节中讲述的“与控制字符以外的字符相匹配”的正则表达式即可。换行符也是控制字符的一种,因此也能被校验到。比如,以下例子中的脚本就是为了确保不包含控制字符并将字符数限制在 1~60。但其前提为内部字符编码为 UTF-8。字符编码不是 UTF-8 时请使用
mb_ereg
函数。if (preg_match('/\A[[:^cntrl:]]{1,60}\z/u', $subject) == 0) { die(' 请输入长度为 1-60 字符的标题 '); }
- 校验邮箱地址
54 关于发送邮件时使用的程序库中的邮件头注入漏洞的情况,可以参考佐名木智贵的在线文档“Security of WebAppli&Mail”[2]。
55 包装函数是指,为了更方便地使用函数或功能而编写的简单的函数。由于是在原函数外包裹了一层使其更容易使用,因此被称为包装函数。
56 http://tools.ietf.org/html/rfc5322
总结
本节讲述了与邮件发送功能相关的安全隐患。
由于多数反馈表单中都会使用发送邮件的功能,因此即便是几乎没有什么功能的应用主页也频频发生与邮件发送功能相关的安全隐患。另外,在网上搜索发送邮件的编程方法时,很容易搜到使用 sendmail 命令这类过时的方法,从而也极易引入安全隐患。
因此,为了避免引入安全隐患,学习 Web 应用中发送邮件的正确方法至关重要。
继续深入学习
为了深入理解与发送邮件相关的安全隐患,对邮件协议(特别是 SMTP)的理解不可或缺。而通过阅读相关的入门书等书籍来学习邮件协议,对解答乱码等问题也很有帮助。
这里向读者们推荐网野卫二所著的《3 分钟 HTTP& 邮件协议基础讲座》[3] 一书,此书同时也可以被作为 HTTP 的入门书使用。
邮件程序库中使用 SMTP 与邮件服务器通信的情况下,可能还会发生 SMTP 命令注入攻击。SMTP 命令注入攻击的实例请参考 NTT Communications 公司发表的《关于 .NET Framework 中的 SMTP Command Injection》[1] 一文。在 .NET Framework 中发送邮件时可能会需要用到文章中讲到的防范策略。
参考文献
[1] NTT Communications.(2011 年 1 月 11 日). .NET Framework 上の SMTP Command Injection について(关于 .NET Framework 中的 SMTP Command Injection). 参考日期:2011 年 1 月 21 日 . 参考网址: /http://www.ntt.com/icto/security/images/sr20110110.pdf
[2] 佐名木智貴 .(2007 年 3 月 27 日). Security of WebAppli&Mail . 参考日期:2010 年 12 月 11 日 . 参考网址: http://rocketeer.dip.jp/secProg/MailSecurity001.pdf
[3] 網野衛二 .(2010).《3 分間 HTTP &メールプロトコル基礎講座》(《3 分钟 HTTP& 邮件协议基础讲座》). 技術評論社 .
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论