如何在邮件发送前在PHP中对用户输入进行净化?

51

我有一个简单的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");
?>

如何对输入进行消毒处理?


1
如果你使用的是纯文本字段,那么在 $_POST 数据中不应该有任何 htmlentities。如果你使用某种生成 html 的富文本编辑器,请使用 html_entity_decode()。确保从主题中剥离控制字符——主题中的换行符可能会破坏你的电子邮件头。 - Frank Farmer
19
@Frank Farmer:你是在暗示他应该相信只要使用“纯文本字段”,就不会有任何冒犯性代码进入他的代码吗?这是很糟糕的建议。 - Carlos Lima
10
嘿,纯文本框。我刚在一个使用这种框的人身上进行了XSS测试。他们不喜欢通过输入在“纯文本框”中的图像链接发送的有趣图片。 - Fiasco Labs
6
未来查看此内容的人,请注意:@FrankFarmer的陈述是错误的。攻击者或其他任何人不仅限于使用您构建的HTML表单来发送数据。有许多方法可以构造针对处理您的表单的脚本的HTTP请求;您应始终假定任何数据都可能进入任何字段。 - Aaron Wallentine
5个回答

53

我喜欢使用filter_var,但不幸的是,并非所有的主机设置都支持5.2版本,但它似乎是一个经过深思熟虑的函数。 - artlung
2
不是所有的主机设置都有5.2,呵呵,那是2009年的事了。现在更多的是,如果他们仍在运行5.4,那就有点可疑了。 - markus
1
确保您传递给mail()函数的任何内容(特别是$headers参数)都不包含未经过滤的文本,尤其是\r\n(header): (...); - Aaron Wallentine

13

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

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

$email = filter_var($email, FILTER_SANITIZE_EMAIL);

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

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

3
电子邮件主题行中的换行符有些棘手。 - Frank Farmer
11
根据电子邮件的构建方式,换行符可以注入到任何邮件头中,使攻击者能够添加任何新的或替换邮件头。此外,注入一个空白行(两个换行符)然后允许攻击者插入他选择的邮件正文,完全覆盖生成的电子邮件。 - Cheekysoft

5
正如其他人所指出的,filter_var很棒。如果它不可用,将此添加到您的工具箱中。
从安全角度来看,$headers变量特别糟糕。它可以被附加并导致伪造的标头被添加。这篇文章称为电子邮件注入 很好地讨论了这个问题。 filter_var很棒,但确保某些内容是电子邮件地址而不是坏东西的另一种方法是使用 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()并限制可输入内容。


1
谈到filter_var,它还有一个用于验证电子邮件的过滤器,因此您的isEmail函数是不好的和不必要的(更不用说正则表达式无法正确验证电子邮件了)。 - user2629998

4
你需要从用户在 $headers 中提供的输入中删除任何换行符,这将传递给 mail() ($email 在你的情况下)!请参见电子邮件注入
PHP 应该负责对 $to 和 $subject 进行消毒,但是有些 PHP 版本存在漏洞(受影响的是 PHP 4 <= 4.4.6 和 PHP 5 <= 5.2.1,请参见MOPB-34-2007)。

1

你可以使用上面 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();

}

邮件()的头部过滤太严格了,因为一些用户可能在其消息中使用被过滤的字符串,而没有任何劫持您的电子邮件表单的意图,因此将其重定向到一个页面,该页面说明不允许在表单中使用哪种字符串或在表单页面上进行解释。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接