PHP 邮件注入防护和电子邮件验证 - 初学者指南
关于各种方法来防护 PHP 邮件头注入和电子邮件验证的指南
引言
关于保护基于PHP的联系表单免受注入攻击的建议有很多,有(略微)不同的方法,以及各种现成的函数和类来帮助你。由于我是新手(本文是为初学者编写的,我自己也属于这一类),我决定更深入地研究尽可能多的建议,特别是查看一些现成解决方案的源代码。
通过这样做,我确信我选择的解决方案对我来说会很好用,更重要的是,我知道我为什么选择那个选项,以及它的优点和局限性。希望你通过阅读本文也能达到这个阶段。我很高兴收到你提供的任何改进建议;这关乎学习和理解。我不是专家;这是为初学者(像我一样)设计的。请不要告诉我它是错的,我很笨,那无助于改进文章或建议,请尽量用“傻瓜式”模式解释,这样我就可以适当地修改文章。
本文也以PHP为重点;毫无疑问,其他语言和混合语言提供了其他选项。PHP是我最初陷入这种困境时正在使用/学习的语言。
目录
总结
如果您不想阅读所有这些内容(它相当长),并且您的情况不关键(例如,它不是电子商务网站),那么我建议使用PEAR MAIL和PEAR HTML_QuickForm2。您将获得良好的注入攻击防护和对用户提供的电子邮件地址格式正确的良好验证(即他们提供的内容至少看起来像一个真实的地址,而不是随机字符),此外您还可以轻松实现对拼写错误等提供良好反馈的机制。
我提供了一个示例,说明如何使用PEAR Mail和PEAR HTML_Quickform2设置联系表单,这对于非关键情况应该很好——我毫不怀疑它可以改进、重构等(因为我不是专业开发人员),但它确实符合“*它有效*”的标准。它也有些丑陋,我添加了一些样式,但不多,这需要你来完成,使其与你网站的其余部分匹配。
关于 PEAR Mail 的另一件事是它最初是为 PHP 4 编写的。在 PHP 5 中,你会收到 `E_STRICT` 错误;这是因为在几个案例中,PEAR Mail 的 `Mail` 类对非静态方法进行了静态方法调用,因此要抑制这些错误,你需要将 PHP 错误报告设置为 `E_ALL`(我在示例中已经这样做了)。如果你需要在网站的其他地方进行严格的错误报告,请记住将其设置回 `E_STRICT`。你可以在这里阅读更多关于该错误的信息。
术语
这两个术语经常出现,每个人似乎都默认同意它们的含义。
净化:在你尝试根据用户提供的数据(通过PHP)发送电子邮件之前,清理或拒绝用户提供的数据(电子邮件地址、主题等)的过程。
验证:确认用户提供的数据(例如电子邮件地址)确实是一个电子邮件地址,而不是随机字符串的过程。
净化是保护您免受我前面提到的那些不良分子滥用的部分,这是关键步骤。验证确保您的用户提供明智的数据,在您的情况下可能并非必要(您是否关心他们告诉您他们的电子邮件地址是_flobble_而不是`bobby.jones@locker.com?`)。
现成解决方案
为什么不直接使用PEAR Mail或PHP Mailer呢?几乎肯定还有其他开源PHP类可以完成同样的工作;我没有超越这两个,仅仅因为它们似乎最受欢迎。或者使用PEAR QuickForm2,它在表单字段到达您的邮件发送PHP代码之前就内置了验证功能。
为什么不直接从 header 数组中移除行尾呢?例如,` (preg_replace('/[\r|\n]+/', “”, $subject)`
好吧,那正是让我困惑的地方。它们都同样好吗?它们是否完全安全?有些比其他更好吗?我查看了代码,但这并没有帮助,因为它们都以不同的方式实现了净化和验证——究竟哪种方法是最好的呢?
嗯,据我所知,最简单的方法,即从 header 数组中剥离行尾,会运行得很好,只是我想你可能需要接受用户输入可能会使你优美简单的代码崩溃,或者你需要添加额外的故障安全处理,这样用户就不会盯着杂乱的 PHP 错误消息发呆——虽然这样做总是明智的,但它可能比你想要的更频繁地被使用!
净化
下面的子章节探讨了 PEAR Mail 和 PHP Mailer 在净化用户输入方面所采取的不同方法,并查看了两个网站提供的建议,这些建议在很大程度上代表了现有的许多建议。关键信息是,如果你不想阅读细节,那就是你永远不应该信任用户输入,并且最低限度应该删除 PHP 邮件功能所需的头部信息中存在的任何行尾。就我个人而言,我可能会使用 PEAR Mail 来为我完成这项工作,而不是自己动手;它看起来是最健壮的实现,如果你想知道为什么,请继续阅读。
PEAR Mail
我们先看看净化,因为这是最关键的。然后我们看看那些大型开源项目的男女开发者们是怎么做的,就当作一个开始。首先是 PEAR Mail 的Mail.php。
function _sanitizeHeaders(&$headers)
{
foreach ($headers as $key => $value) {
$headers[$key] = preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $value);
}
}
好的,PEAR Mail 依赖于剥离行尾,但有多种识别行尾的选项。
主 PEAR Mail `Mail` 类以及 PEAR Mail 使用的各种额外文件,其中包含通过 `include` 引入的附加功能,都有一条来自 2006 年的评论,内容是“_通过净化用户提供的头部信息来防范电子邮件注入漏洞。这通过剥离头部值中任何可能被解释为头部分隔符的内容来完成_。”然后,每个实现此功能的函数都引用了上面显示的 `_sanitizeHeaders` 函数。
PHP Mailer
接着,我们看看 PHP Mailer 的class.phpmailer.php做了什么。
protected function AddAnAddress($kind, $address, $name = '') {
...
...
$address = trim($address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
...
...
}
这个受保护的方法用于添加“To”、“cc”、“Bcc”和“ReplyTo”头部项目,通过`$kind`变量标识。因此,PHP Mailer首先剥离前导和尾随空格,然后移除行尾,但方式与PEAR Mail略有不同。值得注意的是,PHP Mailer还有一个名为`SecureHeader`的函数,它也修剪前导和尾随空格并移除行尾,但使用的是`str_replace`而不是`preg_replace`。它被调用来管理主题头部项目的安全性,但是“From”地址呢?你可能会问。是的,它使用与上面所示的`AddAnAddress`方法相同的代码,但以不同的方法实现。天才,我认为需要一些重构(而我只是个初学者!)
public function SecureHeader($str) {
return trim(str_replace(array("\r", "\n"), '', $str));
}
PEAR Mail 与 PHP Mailer
主要区别似乎在于它们如何精确地移除行尾。危险假设警告:我们暂时假设它们是等效的。是的,我忽略了 PHP Mailer 有点混乱的事实,就本次讨论而言,那些各种行尾替换可以认为是等效的。
另一个不同之处是 PEAR Mail 一次性查看整个 header 数组,而 PHP Mailer 则挑选最终构成 header 数组的单个元素并对其进行检查。
纽约PHP – Phundamentals
那么我们来看看另一个建议来源,纽约PHP网站及其Phundamentals系列,他们在此系列中提出了他们的建议。
他们的第一种方法是使用带有 PHP `preg_match` 函数的 `regex`,建议在提交前使用,即在开始发送功能之前检查用户提供的数据。他们为过滤电子邮件地址提供了一个 `regex`,为其余头部字段提供了另一个 `regex`,如下所示
Pattern for filtering fields such as names
'/^[a-z0-9()\/\'":\*+|,.; \-!?&#$@]{2,75}$/i';
Pattern for filtering email addresses
'/^[^@\s]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';
For example, using the pattern for email addresses, you might do the following:
{$emailPattern = '/^[^@\s]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';
if (!preg_match($emailPattern, $emailFieldToTest)){print 'Please review the email address you entered. There seems to be a problem';
}
由于建议的代码使用了“不匹配”(感叹号),我只能假设他们说的是这些是电子邮件地址唯一有效的字符,并且由于不包含换行符,因此您不会受到注入攻击。这在我看来有点过时,因为可接受域名等的字符集正在爆炸式增长,因此随着时间的推移,该正则表达式必须改变,例如,中文字符成为域名等的允许部分;尽管这篇文章似乎写于2005年左右,所以这不是批评,我敢猜测作者今天会采取不同的方法。
他们的第二种方法看起来更熟悉,旨在在提交后工作。
function safe( $name ) {
return( str_ireplace(array( "\r", "\n", "%0a", "%0d", "Content-Type:", "bcc:","to:","cc:" ), "", $name ) );
}
在这种情况下,又是老式的 `str_replace`,或者是 PHP 5 变体 `str_ireplace`。它们只查找 PEAR Mail 查找的一部分行结束字符,但比 PHP Mailer 查找的更多。
Dream.in.code,由codeprada撰写的教程
我们再来看一个选项,一篇由codeprada在Dream.in.code上发表的文章。
这看起来与纽约PHP的第二个建议相似,但略有不同。
function sanitize(&$array) {
foreach($array as &$data)
$data = str_replace(array("\r", "\n", "%0a", "%0d"), '', stripslashes($data));
}
}
我不确定为什么要包含 `stripslashes` 函数,但我认为这里的核心是它与纽约PHP的建议相似。与 PEAR Mail 一样,它遍历整个数组,而不是挑选特定的头部元素。
PHP filter_var 和 FILTER_SANITIZE_EMAIL
在研究验证电子邮件地址时,我偶然发现了一项内容,那就是 PHP 的 `filter_var` 函数。你可以使用它附带的大量常量来自动过滤变量,其中一个预定义常量是 `FILTER_SANITIZE_EMAIL`。这对其他头部项目用处不大,但电子邮件呢?也许这是你允许用户提供的所有内容,因此是你需要测试的所有内容?
手册页上写道:“_删除除字母、数字和!#$%&'*+-/=?^_`{|}~@.[]之外的所有字符_”。我不太喜欢这个,或者至少对此不自信,原因与纽约PHP提供的第一种方法类似,而且解释也很少,“所有字符”是否包含当前和未来的所有字符集,第二部分是否作为正则表达式应用?我找不到PHP源代码的相关部分来尝试理解。这里还有另一个可能相关的评论,`FILTER_SANITIZE_EMAIL`实际上会更改你传递给`filter_var`的变量,因此如果用户打错了字(而不是注入攻击)并且你尝试更正它,谁知道你更正的版本是否是用户想要的?我认为在这种情况下,最好向用户提供反馈并允许他们更正。
净化总结
所以,移除行尾似乎是关键,正如我们上面提到的,但有不同的方法来做到这一点,以及在多大程度上可以去寻找行尾字符。我完全赞成尽可能多地进行测试,这似乎不太可能增加太多的处理时间开销,只要它不会导致意外的故障或误报,那么在我看来,这听起来很明智。
我从查看所有这些代码中注意到的另一件事是,PEAR Mail 的方法(由 Dream.in.code 文章复制)在 `foreach` 循环中净化所有头部,看起来更容易编写代码,更容易审查,更容易查看实际发生的情况,也更容易维护。
总结每个选项每个方法试图处理的行尾
方法 | 行尾字符 | 空格 | |||||||
---|---|---|---|---|---|---|---|---|---|
/n | /r | <CR> | <LF> | 0x0A | %0A | 0x0D | %0D | ||
PEAR | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | - |
PHP Mailer | 是 | 是 | - | - | - | - | - | - | 是 |
纽约 PHP | 是 | 是 | - | - | - | 是 | - | 是 | - |
Dream.in.code | 是 | 是 | - | - | - | 是 | - | 是 | - |
我不知道删除空格是否是个好主意,甚至是否有用。
鉴于我对 PHP 的深入理解基本上是空白的,那么完全有可能某些 PHP 设置意味着只检查一组行结束字符就足够了,因为它们可能都被转换为例如 ` /r/n`,或者 `str_replace`、`str_ireplace` 和 `preg_match`、`preg_replace` 可能有一种方法可以给定一种行结束类型,然后以相同的方式处理它的所有变体。如果能知道这一点就太好了。
无论如何,我会说那是 PEAR Mail 的胜利。这会让我感觉更舒服。
验证
知道你的网站受到注入攻击的保护是一回事,但如果用户告诉你他们的电子邮件地址是_not_telling_you_,那么优雅地失败呢?你将没有地址可回复。同样,如果由于某种原因,用户被允许提供“To”地址(或“cc”和“bcc”),你很可能希望在尝试发送邮件之前验证它实际上是一个有效的电子邮件地址。这再次存在差异。
我们将不考虑纽约PHP网站提供的伪验证(它本质上也是为了净化)——那个只检查有效字符的验证,因为无法知道它是否是格式正确的电子邮件地址,所以我们来看看PEAR Mail、PHP Mailer和`filter_var`(这次使用`FILTER_VALIDATE_EMAIL`)。
同样值得注意的是,在此上下文中,验证意味着检查它是否是格式正确的电子邮件地址,并不意味着检查该电子邮件地址是否真实存在,并且某个地方有一个活生生的人会查看它。
如果你不想阅读细节,总结就是:很难做出一个单一(且简单)的“正确”选择。如果将地址验证为真实、格式正确对你的网站至关重要,那就更难了。如果对你至关重要,建议寻求专业建议。如果不是关键,那么 PEAR HTML_QUICKFORM2 和 PEAR Mail 的组合是一个不错的选择,它将最大限度地减少你的麻烦,并在绝大多数情况下都能正常工作。
健康警告
这是一个相当模糊的领域——不是贬义上的模糊,只是连基本原理都很难掌握,因为你一旦开始深入挖掘,就会发现各种把你引向相当复杂境地的事情。例如,哪个标准定义了什么是可接受的电子邮件地址,什么不是?PEAR Mail 有一个名为 `Mail_RFC822` 的类,它自称为“_RFC 822 电子邮件地址列表验证工具_”。好的,那么这就是标准了——`RFC 822`,还是不是?根据Dominic Sayers的说法,它是 `RFC 5321`,请查看他在isemail网站上的关于页面。Isemail 是一个 PHP 函数,托管在code.google.com上,声称可以根据 `RFC 5321` 验证电子邮件地址。
那个类(PEAR MAIL 中的 `RFC 822`)和那个函数(IsEmail 中的 `RFC 5321`)都太过复杂,我甚至无法考虑评论它们各自的有效性或正确性。我不知道 `RFC 822` 或 `RFC 5321` 是我应该担心的,更不用说这两个选项是否真的完全验证了标准所描述的内容,甚至这些标准是否以一种可以明确理解什么是有效和什么是无效的方式编写的。
如果你再看看 PHP 中的 `filter_var` 函数和 `FILTER_VALIDATE_EMAIL` 常量,情况会变得更加令人困惑。它使用正则表达式来验证电子邮件,而不是 PEAR Mail 和 IsEmail 使用的 >1000 行代码,有人愿意确定一个包含超过 1000 个字符的正则表达式是否完全符合这些标准中的一个要求,或者该正则表达式比 >1000 行代码更好还是更差?甚至该正则表达式也是其他人提供的一个变体。
许多争论似乎都围绕着是否允许仅带有顶级域名 (TLD) 的电子邮件地址进行验证,例如 bob@au,这可以吗?如果是本地电子邮件而不是跨域电子邮件呢?然后还有一堆其他的讨论,我无意深入理解,就像我无意尝试理解 RFC 标准一样。只知道这并不简单,你应该仔细选择,特别是如果它是电子商务网站上的注册表单(参见下面的验证和 Gmail 风格地址部分以获取示例),因为你真的不想拒绝潜在客户提供的有效电子邮件地址。
PEAR Mail
如上所述,PEAR Mail 实现了一个名为 `Mail_RFC822` 的类,用于验证 header 中的电子邮件地址是否与 `RFC 822` 定义的内容一致(或不一致)。它不依赖任何 PHP 功能,例如 `filter_var`。然而,它确实使用 `preg_replace` 处理行尾,为什么这是必要的我不知道,因为 PEAR Mail 的 `Mail` 类本身已经这样做了。
// Unfold any long lines in $this->address.
$this->address = preg_replace('/\r?\n/', "\r\n", $this->address);
$this->address = preg_replace('/\r\n(\t|)+/', ' ', $this->address);
也许这与 `Mail_RFC822` 类处理数组的方式有关,或者只是重复功能——甚至可能是在没有 PEAR Mail 的情况下使用该类时进行的最小健全性检查(净化)。
我无法评论它与 `RFC 822` 要求的匹配程度,也无法评论对照该标准而非 `RFC 5321` 进行验证是正确还是错误,或者它是否会带来任何实际影响。
我们或许可以说,既然 PEAR Mail 被广泛使用,那么如果它踢掉了很多有效电子邮件地址或允许无效地址通过,那很可能会被大量评论为错误。你能找到的唯一直接相关的错误是这个,它请求在测试环境等特定情况下抑制验证的能力。其中有一条评论说“_验证功能确实非常不可靠_”,我不知道这个批评的有效性如何——它可能完全是无稽之谈。
PHP Mailer
PHP Mailer 有一个单一而简单的电子邮件验证函数,如下所示。
/**
* Check that a string looks roughly like an email address should
* Static so it can be used without instantiation
* Tries to use PHP built-in validator in the filter extension (from PHP 5.2), falls back to a reasonably competent regex validator
* Conforms approximately to RFC2822
* @link http://www.hexillion.com/samples/#Regex Original pattern found here
* @param string $address The email address to check
* @return boolean
* @static
* @access public
*/
public static function ValidateAddress($address) {
if (function_exists('filter_var')) { //Introduced in PHP 5.2
if(filter_var($address, FILTER_VALIDATE_EMAIL) === FALSE) {
return false;
} else {
return true;
}
} else {
return preg_match('/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!\.)){0,61}[a-zA-Z0-9_-]?\.)+[a-zA-Z0-9_](?:[a-zA-Z0-9_\-](?!$)){0,61}[a-zA-Z0-9_]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/', $address);
}
}
我认为函数中的代码注释已经说明了我所有想说的话,我们就此打住,除了下面我对`FILTER_VALIDATE_EMAIL`的评论。
我一点也不确定是否想依赖这个,除非您想使用一个可能存在 bug 的 PHP 函数(但请参阅下面的讨论,因为它可能不再那么有 bug 了),它会退回到一个“_相当称职_”且“_大致符合_”的实现。随你便吧,但如果有效电子邮件不是关键,那么我可能会倾向于不进行验证,如果它们是关键,那么就另寻他法。
Filter_var 和 FILTER_VALIDATE_EMAIL
我_可以_简单地说,关于`FILTER_VALIDATE_EMAIL`,除了我在上面健康警告部分提到的之外,我只想补充一点,它似乎与您使用的PHP版本有很强的依赖性。该常量背后使用的正则表达式,并与`filter_var`函数一起使用,在PHP的最新版本中经过了一些更改以修复一些bug,尽管有些人似乎争辩说那些根本不是bug。正如PHP Mailer所指出的,如果有效电子邮件不是关键,那么我可能会倾向于完全不进行验证;如果它们是关键,那么就另寻他法,然而……
这条评论的反面有两个方面;
- 在 PHP 源代码的注释部分,它提到了 `RFC 5321`,所以如果 Dominic 说得对,那么至少它指向了正确的标准,并且
- 在 IsEmail 的评论中,有人使用了 IsEmail 基于 XML 的测试地址(声称涵盖所有 `RFC 5321` 接受和不接受的变体)来对抗 `FILTER_VALIDATE_EMAIL`,结果除了一个只有顶级域名(TLD)的地址外,所有都通过了;一些人认为 `RFC 5321` 不允许此类地址,另一些人则认为允许。
那么也许它是个不错的选择?(如果您的服务器上安装了 PHP 5.2+)。不容易吧!
IsEmail
我们之前提到过它,所以它可能值得拥有自己的部分。如果您决定确实需要针对 `RFC 5321` 进行验证,而不是更受欢迎的 `RFC 822`(无论哪个是正确的选择),那么我还没有遇到任何声称可以做到这一点而无需诉诸于难以理解的正则表达式(如 `FITER_VALIDATE_EMAIL` 所做),尽管我无法理解该函数的代码,所以这本质上也是一个任意的差异。
我想我唯一的其他评论是,这不是一个维护中的开源项目,所以随着时间的推移,它面临过时的风险。不过在某些情况下,它可能是一个不错的选择。如果能将其整合到 PEAR Mail 中作为 `Mail_RFC822` 的替代方案就更好了,这样你就可以选择要验证的标准,并且代码也能得到维护。或者甚至将其整合到 PHP 本身,也许通过 `filter_var`,你可以拥有 `FILTER_VALIDATE_RFC822_EMAIL`、`FILTER_VALIDATE_RFC5321_EMAIL` 等。
Gmail 风格
IsEmail 的宣传语中有一个关于 Gmail 的有趣评论,它似乎允许bobby.jones@gmail.com以bobby.jones+codeproject@gmail.com的形式向需要注册的网站提供其电子邮件地址。令人兴奋!邮件将发送到bobby.jones@gmail.com的标准邮箱,但您可以轻松看出邮件应该来自哪个网站,因此如果收到发送到 +codeproject 版本的 Viagra 广告,您就会知道他们要么出售了您的电子邮件(肯定不是!),要么他们的数据库已被泄露,并可以阻止(或过滤)发送到您 Gmail 收件箱的该变体的更多邮件。
那么,在讨论的电子邮件验证技术中,哪种能验证上述 Gmail 功能?根据我的知识水平,我不确定这个问题是否容易回答,即使你知道那是有效的 `RFC 5321`,你又怎么知道 `RFC 5321` 验证器是否能正确验证它(以及所有其他不明显的变体)?正如之前所说,如果你的网站是电子商务网站或依赖大量用户群从广告中获取收入的网站,你不会想拒绝有效的电子邮件地址。请谨慎行事。
其他说明
再看 IsEmail 的宣传语(这次是在评论区,但作者本人写的),上面写道:“_最新的变化是,浏览器制造商决定完全可以忽略 RFCs,自行重新定义什么是有效的电子邮件。在创建 INPUT TYPE="email" 时,他们决定只允许简单验证的电子邮件地址格式的简化子集。这种临时标准有效地重新定义了现实世界中什么是有效的地址_”。
这里的情况并没有变得更容易。
验证总结
很难知道该写些什么。我们从简单开始。也许一个好方法是要求用户回复发送到他们提供的电子邮件地址的邮件以完成注册?这种情况经常发生,用户也习惯了,所以这似乎是一个合理的方法,只要你净化了你的头部,你就不需要验证电子邮件,用户通过回复邮件为你完成了。然而
- 用户都是一群急躁的人,我怀疑随着时间的推移,他们会越来越不愿意忍受这种情况,特别是对于电子商务网站来说,他们只想完成交易,你设置的任何障碍都可能导致客户流失,对于像 codeproject 这样的网站来说,这可能不是一个大问题,因为注册的动机不同(尽管我记得我**不**需要在 codeproject 的情况下进行验证);
- 帮助用户识别他们何时打错了地址通常是一件好事,尤其现在他们习惯了在网站表单上即时反馈,突出遗漏和错误,有时是出于安全原因(例如银行网站),有时只是为了用户友好。
我**不会**使用双重输入作为您的验证方法,难道现在还有人不用剪切和粘贴来完成这个吗?那些阻止您使用剪切和粘贴的网站呢?我讨厌它们,真的,它们让我烦透了,别那样做。
但如果,无论出于何种原因,电子邮件验证对你来说至关重要。你如何选择合适的工具、类等来帮助你——我无法想象你会想自己编写一个包含数千行 PHP 代码的类!根据我有限的经验和这项调查,我可能会说你可以实现自己的函数,基于 PHP Mailer 使用的那个,是的,我知道我之前批评过它,但请听我说。
如果我们假设 `RFC 5321` 是您应该进行验证的标准,并且关于 `filter_var` 和 `FILTER_VALIDATE_EMAIL` 改进功能的评论是有效的——即它在根据 `RFC 5321` 验证电子邮件地址方面做得很好(忽略关于 TLD 的分歧),那么如果一个网站安装了最新版本的 PHP 5(5.2 或更高版本),您就拥有了一个很好的验证机制。我所做的更改是针对其回退选项。它选择了一个“_相当称职_”的 `RFC 822` 验证实现,为什么不将该回退替换为 `filter_var` 与 `FILTER_VALIDATE_EMAIL` 相同的手动实现呢?正则表达式在 PHP 源代码中定义,所以应该不难采用(尽管我不是 `C` 或 `PCRE` 专家,也肯定无法将其转换为 PHP 代码)。然后,无论您使用什么版本的 PHP,您都拥有 `RFC 5321` 风格的验证。
我在本摘要中假定 `RFC 5321` 是正确的选项,例如 PHP 核心代码已经通过 `FILTER_VALIDATE_EMAIL` 走向了这条路。我想 PEAR Mail 使用 `RFC 822` 感觉有点奇怪,但是如果我要做一个假设(初学者通常需要做很多假设),那么这就是我会做的那个。尽管我怀疑使用 PEAR Mail 不会出大错,_而且在总结这次讨论时我改变主意了_ :-)
PEAR QuickForm2
我一直在使用这个,并开始思考它允许你应用于表单字段的规则,例如下面显示的那些,是否提供了所需的部分或全部净化和验证,以及它与我们已经讨论过的内容相比如何。
$e_mail->addRule('required', 'Your E-Mail is required', null, HTML_QuickForm2_Rule::ONBLUR_CLIENT_SERVER);
$e_mail->addRule('email', 'Email address is invalid', null, HTML_QuickForm2_Rule::ONBLUR_CLIENT_SERVER);
需要立即指出的是,我不知道这在多大程度上能够抵御基于脚本的攻击,例如,如果有人向您的网站发送了一个伪造的 `http` 请求,其中包含手动(恶意)创建的 `POST` 或 `GET` 信息,`QuickForm2` 规则会起作用吗?或者它们只对手动方法起作用,例如键盘上的不良分子或伪造的提交?
一个粗略的测试,通过在 PHP 表单的 from 字段中添加 _%0ABcc:myemail@emailhost.com_,并使用上述 `$e_mail` 验证规则,证实它拒绝了这种将电子邮件发送给意外收件人的方式,但没有正式的净化,这让我有点不安。查看 QuickForm2 规则的文档,特别是 `$e_mail` 规则,也让我感到不安;它看起来只是我们一直在讨论的内容的一个非常部分的实现。
看起来不是一个快速简单的解决方案。特别是,我不知道如何将净化规则应用于其他字段,例如 `Subject:` 或 `To:`,至少在不自行编写规则的情况下是如此,QuickForm2 允许您这样做,但这不一定是我的乐趣所在。
总结
在大多数情况下,请避免使用 `FILTER_SANITIZE_EMAIL`,除非您乐于让它修改您的用户输入并忽略简单打字错误的可能性。
请谨慎使用 PHP Mailer(当前形式)。
如果您要自行开发,那么请务必清楚自己在做什么,因为似乎很容易在您的防御中留下一个巨大的漏洞,而一些不法分子很可能找到突破口,然后您就会陷入困境,可能面临高额带宽费用、被列入黑名单、被您的主机提供商阻止等等。
非关键情况
这描述了我可能遇到的 99.999% 的场景(这是一个保守估计)。在这种情况下,我认为 PEAR Mail 净化是我的首选,我会接受验证步骤为 `RFC 822`(以及我对该验证实施效果的缺乏了解)。我不认为 `RFC 822` 验证和 `RFC 5321` 验证之间的差异会给我带来大麻烦。我当然可能错了,但由于 PEAR Mail 广泛使用,我认为我的假设可能是安全的。
我也可能使用 QuickForm2 规则,因为我可以应付偶尔的误报,而且我喜欢它提供用户反馈的方式——尽管如果你不使用 QuickForm2,你可能可以省去这一步,或者自己编写 `JavaScript` 来为你提供用户反馈,也许 `jQuery` 已经做到了(但为什么麻烦呢,要使用 PEAR Mail,你必须安装 PEAR,所以也安装 QuickForm2 吧)。
但请务必使用某种方法来净化您的头部信息!
当它_非常_关键时
这有点棘手。如果 PHP Mailer 解决了其效率低下且笨重的净化实现,并且您使用的是最新版本的 PHP,那么它看起来会非常有吸引力。如果您安装的是旧版本的 PHP,那么如果 PHP Mailer 用一个为 `RFC 5321` 的健壮实现而设计的验证回退来替换当前的验证回退,那么它似乎是一个非常好的选择。您将非常安全地免受注入攻击,并将最大限度地降低误报验证失败的风险。
鉴于 PHP Mailer 没有做上述任何一件事,那我就会寻求比我能提供的更专业的建议!
关注点
我热爱开源。我讨厌试图理解由 n 个不同人编写的代码。
历史
- 版本 5:小幅更新
- 更新了引言,提高了重点和清晰度
- 版本 4:原创文章首次发布
- 版本 1-3:预发布编辑