如何在邮件发送前清理 PHP 中的用户输入?

Posted

技术标签:

【中文标题】如何在邮件发送前清理 PHP 中的用户输入?【英文标题】:How to sanitze user input in PHP before mailing? 【发布时间】:2010-11-06 12:40:54 【问题描述】:

我有一个简单的 php 邮件脚本,它从通过 POST 提交的表单中获取值并将它们邮寄给我:

<?php
$to = "me@example.com";

$name = $_POST['name'];
$message = $_POST['message'];
$email = $_POST['email'];

$body  =  "Person $name submitted a message: $message";
$subject = "A message has been submitted";

$headers = 'From: ' . $email;

mail($to, $subject, $body, $headers);

header("Location: http://example.com/thanks");
?>

如何清理输入?

【问题讨论】:

如果您使用的是直列文本字段,则 $_POST 数据中不应有任何 htmlentities。如果您使用某种生成 html 的富文本编辑器,请使用 html_entity_decode()。一定要去掉主题中的控制字符——主题中的换行符会搞砸你的电子邮件标题 @Frank Farmer:你是说他应该相信没有攻击性代码会因为他使用“直接文本字段”而访问他的代码吗?这是一个糟糕的建议。 嘿,直截了当的文本字段。刚刚对这样做的人进行了 XSS 测试。他们不喜欢通过在“直接文本字段”中输入的 img 链接发送的有趣图片。 对于将来查看此内容的任何人的记录,@FrankFarmer 的声明是不正确的。攻击者或任何其他人不仅限于使用您构建的 HTML 表单来发送数据。有很多方法可以针对处理表单的脚本构造 HTTP 请求;您应该始终假设任何数据都可能进入任何领域。 【参考方案1】:

使用 filter_var() 清理 post 变量。

Example here。喜欢:

echo filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);   

【讨论】:

我喜欢 filter_var ,遗憾的是,并非所有托管设置都有 5.2,但它似乎是一个经过深思熟虑的功能。 并不是所有的主机都安装了 5.2,呵呵,2009 年就是这样。现在更像是如果他们还在运行 5.4,他们就很可疑。 确保您传递给 mail() 函数的任何内容(尤其是 $headers 参数)都不能包含未经过滤的文本,尤其是\r\n(header): (...);【参考方案2】:

由于您没有在此处构建 SQL 查询或任何内容,因此我可以看到的对这些输入的唯一相关验证是 $_POST["email"] 的电子邮件验证,如果是其他字段,则可能是字母数字过滤器你真的想限制消息可以包含的范围。

要过滤电子邮件地址,只需使用filter_var:

$email = filter_var($email, FILTER_SANITIZE_EMAIL);

根据 Frank Farmer 的建议,您还可以过滤掉电子邮件主题中的换行符:

$subject = str_replace(array("\r","\n"),array(" "," "),$subject);

【讨论】:

电子邮件主题行中的换行符有些问题。 根据电子邮件的构造方式,可以将换行符注入任何电子邮件标头,从而允许攻击者添加他喜欢的任何新的或替换的电子邮件标头。除此之外,注入一个完整的空白行(两个换行符)然后允许攻击者插入他选择的电子邮件正文,完全覆盖生成的电子邮件。【参考方案3】:

正如其他人所指出的,filter_var 很棒。如果它不可用,请将其添加到您的工具箱中。

$headers 变量的安全性特别差。它可以附加到并导致添加欺骗标题。这篇名为Email Injection 的帖子讨论得很好。

filter_var is 很好,但另一种确保某事是电子邮件地址而不是坏事的方法是使用isMail() 函数。这是一个:

function isEmail($email) 
    return preg_match('|^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]2,)+$|i', $email);
;

所以要使用这个,你可以这样做:

if (isset($_POST['email']) && isEmail($_POST['email'])) 
    $email = $_POST['email'] ;
 else 
    // you could halt execution here, set $email to a default email address
    // display an error, redirect, or some combination here,

在手动验证方面,使用substr()限制长度,运行strip_tags(),否则限制可以放入的内容。

【讨论】:

说到 filter_var,它还有一个过滤器来验证电子邮件,所以你的 isEmail 函数是不好的和不必要的(更不用说正则表达式不能正确验证电子邮件)。【参考方案4】:

您需要从用户在 $headers 中提供的输入中删除所有换行符,这些换行符会传递给 mail()(在您的情况下为 $email)!见Email injection。

PHP 应该负责清理 $to 和 $subject,但有些版本的 PHP 存在错误(受影响的是 PHP 4 MOPB-34-2007)。

【讨论】:

【参考方案5】:

您可以使用上面artlung 的答案中的代码来验证电子邮件..

我使用这种代码来防止标头注入..

// define some mail() header's parts and commonly used spam code to filter using preg_match
$match = "/(from\:|to\:|bcc\:|cc\:|content\-type\:|mime\-version\:|subject\:|x\-mailer\:|reply\-to\:|\%0a|\%0b)/i";

// check if any field's value containing the one or more of the code above
if (preg_match($match, $name) || preg_match( $match, $message) || preg_match( $match, $email)) 

// I use ajax, so I call the string below and send it to js file to check whether the email is failed to send or not
echo "failed";

// If you are not using ajax, then you can redirect it with php header function i.e: header("Location: http://example.com/anypage/");

// stop the script before it reach or executing the mail function
die();


上面mail() 的标头过滤过于严格,因为有些用户可能在他们的邮件中使用过滤后的字符串,而无意劫持您的电子邮件表单,因此将其重定向到解释什么样的字符串的页面表格中不允许的内容或在您的表格页面上进行解释。

【讨论】:

以上是关于如何在邮件发送前清理 PHP 中的用户输入?的主要内容,如果未能解决你的问题,请参考以下文章

PHP 向用户在插件文件中输入的地址发送电子邮件

如何在php中的特定日期或时间自动发送电子邮件?

如何在 php 中向数千名用户发送安全(爆炸)电子邮件?

如何在 PHP 中向用户发送每日电子邮件通知?

mail邮件

在 php 中清理输入和输出的圣杯?