什么是最好的PHP输入过滤函数?

176
我正在尝试编写一个函数,可以将所有字符串传递到其中进行清理,以便从中得到的字符串在插入数据库时是安全的。但是有很多过滤函数,我不确定我应该使用/需要哪些过滤函数。
请帮我填空:
function filterThis($string) {
    $string = mysql_real_escape_string($string);
    $string = htmlentities($string);
    etc...
    return $string;
}

4
对于插入操作,只使用mysql_real_escape_string来防止SQL注入是可行的。但当你在使用SELECT查询到的数据时(用于HTML输出或PHP公式/函数中),应该使用htmlentities函数处理。 - davidosomething
1
参见 https://dev59.com/oHVD5IYBdhLWcg3wL4cA#60496,其中有一个特定于数据库插入清理的答案(它给出了 PDO 的示例,其他人在下面也提到了)。 - Pat
14个回答

473

停止!

你在这里犯了一个错误。哦,不,你选择了正确的PHP函数使你的数据更加安全。这很好。你的错误在于操作顺序,以及如何和何处使用这些函数。

理解清洁和验证用户数据、为存储转义数据以及为演示转义数据之间的区别非常重要。

清洁和验证用户数据

当用户提交数据时,你需要确保他们提供了你期望的内容。

清洁和过滤

例如,如果你期望一个数字,确保提交的数据是数字。你还可以将用户数据转换为其他类型。所有提交的数据最初都被视为字符串,因此将已知的数值数据强制转换为整数或浮点数可以使清洁变得快速而简单。

那么自由格式文本字段和文本区域呢?你需要确保这些字段中没有意外的内容。主要是确保不应该包含任何HTML内容的字段实际上不包含HTML。你有两种方法来处理这个问题。

首先,您可以尝试使用htmlspecialchars转义HTML输入。不应该使用htmlentities来中和HTML,因为它还会对重音和其他字符进行编码,认为这些字符也需要进行编码。

其次,您可以尝试删除任何可能的HTML。strip_tags快速简便,但也比较粗糙。HTML Purifier可以更彻底地剥离所有HTML,并允许有选择性的标签和属性通过。

现代PHP版本附带了the filter extension,提供了一种全面的方式来清理用户输入。

验证

确保提交的数据没有意外内容只是工作的一半。您还需要尝试确保提交的数据包含您实际可以使用的值。

如果您期望一个1到10之间的数字,您需要检查该值。如果您正在使用其中一个新型的HTML5时代的数值输入框,带有微调器和步骤,请确保提交的数据符合步骤。

如果该数据来自应该是下拉菜单的选项,请确保提交的值是出现在菜单中的值。

那么如何处理满足其他需求的文本输入呢?例如,日期输入应该通过strtotimeDateTime类进行验证。给定的日期应该在你预期的范围内。那么电子邮件地址呢?前面提到的filter extension可以检查地址是否格式正确,但我更喜欢the is_email library

对于所有其他表单控件也是如此。有单选按钮吗?根据列表进行验证。有复选框吗?根据列表进行验证。有文件上传吗?确保文件是预期类型,并像未经过滤的用户数据一样对待文件名。

每个现代浏览器都配备了完整的开发人员工具,使任何人都可以轻松操纵您的表单。您的代码应该假设用户已完全删除了表单内容上的所有客户端限制

为存储转义数据

现在,您已确保数据符合预期格式且仅包含预期值,您需要担心将该数据持久化到存储中。

每种数据存储机制都有一种特定的方法来确保数据被正确转义和编码。如果您正在构建SQL,则传递查询中的数据的接受方式是使用prepared statements with placeholders

在PHP中与大多数SQL数据库一起使用的更好的方法之一是PDO扩展。它遵循准备语句将变量绑定到语句,然后将语句和变量发送到服务器的常见模式。如果你以前没有使用过PDO,这里有一个相当不错的面向MySQL的教程

一些SQL数据库在PHP中有自己的专业扩展,包括SQL ServerPostgreSQLSQLite 3。这些扩展都支持准备好的语句,操作方式与PDO相同。有时候,你可能需要使用这些扩展来支持非标准的特性或行为,而不是使用PDO。

MySQL也有自己的PHP扩展,实际上有两个。你只需要使用名为mysqli的那个。旧的"mysql"扩展已经被deprecated,在现代时代不安全也不明智。
我个人不太喜欢mysqli。它在准备好的语句中执行变量绑定的方式不灵活,使用起来可能很麻烦。如果不确定,最好使用PDO。
如果您没有使用SQL数据库存储数据,请查阅您正在使用的数据库接口的文档,以确定如何安全地通过它传递数据。
在可能的情况下,请确保您的数据库将数据存储在适当的格式中。在数字字段中存储数字,在日期字段中存储日期,在十进制字段而不是浮点字段中存储货币。请查看数据库提供的有关如何正确存储不同数据类型的文档。
呈现数据时必须确保数据已安全转义,除非您知道不需要转义。
当发出HTML时,您几乎总是应该通过htmlspecialchars传递任何原始由用户提供的数据。事实上,唯一不需要这样做的时候是当您知道用户提供了HTML,并且您知道已经使用白名单对其进行了净化。
有时需要使用PHP生成一些Javascript。 Javascript没有与HTML相同的转义规则! 通过json_encode以PHP提供用户提供的值是一种安全的方式来传递给Javascript。

更多内容

数据验证还有许多微妙之处。例如,字符集编码可能是一个巨大的陷阱。您的应用程序应遵循“UTF-8 all the way through”中概述的做法。当您将字符串数据视为错误的字符集时,可能会发生假想攻击。
我之前提到了浏览器调试工具。这些工具也可以用于操作Cookie数据。 Cookie应该被视为不可信任的用户输入。
数据验证和转义只是Web应用程序安全的一个方面。您应该使自己了解web application attack methodologies,以便您可以针对它们建立防御措施。

在指定编码时,请确保它在支持的编码列表中。 - Charles
3
请将文本从英语翻译成中文。仅返回翻译后的文本:不要使用htmlentities,而是用htmlspecialchars替换它,以替换<>,而不是每个字符都替换为实体。 - Your Common Sense
7
请确保不要重复调用htmlspecialchars,因为他在“当用户提交数据部分”和“当显示数据时”部分都提到了它。 - Savageman
2
点赞。这是我在许多关于SQL注入的问答中读到的最有帮助的答案。 - akinuri

35

预防SQL注入最有效的方法是使用 PDO 参数化。使用参数化查询,将查询和数据分开,因此消除了一级SQL注入的威胁。

如果需要移除HTML标签,strip_tags 可能是移除HTML标签的最佳选择,因为它会完全删除所有标签。而htmlentities 的功能则像它的名字所描述的那样。如果您需要解析允许使用哪些HTML标签(即,您想允许某些标签),则应使用已存在的成熟的解析器,例如HTML Purifier


5
你认为只有在输出时才应该去除HTML吗?在我看来,你永远不应该更改输入数据——因为你永远不知道何时会需要它。 - Joe Phillips

11

数据库输入 - 如何防止 SQL 注入攻击

  1. 检查数据类型是否正确,例如整数类型必须确保为整数
    • 对于非字符串数据,需要确保其为正确的数据类型
    • 对于字符串数据,需要在查询中用引号将字符串括起来(显然,否则它甚至都无法工作)
  2. 在避免 SQL 注入攻击的情况下将值输入数据库(使用 mysql_real_escape_string 或参数化查询)
  3. 从数据库中检索值时,请确保 HTML 不能被注入到页面中,以避免跨站脚本攻击(使用 htmlspecialchars)

在将用户输入插入或更新到数据库之前,需要对其进行转义处理。这是一种较旧的方法,现在最好使用参数化查询(可能来自 PDO 类)。

$mysql['username'] = mysql_real_escape_string($clean['username']);
$sql = "SELECT * FROM userlist WHERE username = '{$mysql['username']}'";
$result = mysql_query($sql);

从数据库输出 - 如何防止XSS(跨站脚本攻击)

仅在从数据库输出数据时使用htmlspecialchars()。对于HTML Purifier也是如此。例如:

$html['username'] = htmlspecialchars($clean['username'])

最后...

我必须指出,如果您使用参数化查询的PDO对象(正确的方法),则真正实现此功能是没有简单方法的。但是,如果您使用旧的“mysql”方法,则需要这样做。

function filterThis($string) {
    return mysql_real_escape_string($string);
}

为什么只有在从数据库输出数据时才使用htmlspecialchars()函数? - dmuensterer
@dmuensterer 因为通常不希望将 HTML 存储在数据库中。 - Joe Phillips

7

我的看法。

这里没有人理解 mysql_real_escape_string 的工作方式。 该函数不过滤或“清洗”任何内容。
因此,您不能将此函数用作通用过滤器,以免受到注入攻击的影响。
只有在您理解它的工作原理和适用范围时才能使用它。

我已经写了一篇非常类似的问题的答案: 在PHP中提交字符串到数据库时,我应该使用htmlspecialchars()处理非法字符还是使用正则表达式?
请点击获取有关数据库安全性的完整解释。

至于htmlentities - Charles 是正确的,告诉你要将这些功能分开使用。
想象一下,您将插入由管理员生成并允许发布HTML的数据,您的函数会破坏它。

虽然我建议不要使用 htmlentities。这个函数很久以前就过时了。如果您想仅替换 <, >, 和 " 字符以保证 HTML 安全性 - 使用专为此目的开发的函数 - htmlspecialchars()


1
mysql_real_escape_string函数可以转义字符串中需要转义的字符。它并不是严格意义上的过滤或净化,但是在引号中包含一个字符串也不是(而且每个人都这样做,我几乎从未看到有关此问题的疑问)。 那么当我们编写SQL时,没有任何净化吗?当然不是。防止SQL注入的方法是使用mysql_real_escape_string函数。还有引号,但是每个人都这样做,如果您测试自己的代码,忽略引号会导致SQL语法错误。真正危险的部分由mysql_real_escape_string函数处理。 - Savageman
尝试使用“WHERE my_field = two words”(不包括引号)来获得语法错误。你的例子是错误的,因为它既不需要引号也不需要转义,只需要一个数字检查。 此外,我并没有说引号是无用的。我说每个人都使用它们,所以这不是关于SQL注入问题的根源。 - Savageman
1
@Savageman,我说过:“只有在你理解它的工作原理和适用范围时才能使用它。”你刚才承认mysql_real_escape_string并非适用于所有情况。至于“每个人都使用它们”,你可以在SO上检查代码。许多人不会在数字中使用引号。自己看吧。请记住,我在这里不是讨论你说了什么或者你没有说什么。我只是在解释基本的数据库安全规则。你最好学习而不是空洞争论。这里没有人提到引号或转换,只有m_r_e_s被当作魔法一样提到。我在谈论什么。 - Your Common Sense
是的,你说得对,但我认为你把一件小事看得太严重了。引号不会被忘记,因为它们很快就会引起大问题(比如语法错误)。它们不会被忽视,不像 mysql_real_escape_string()。如果你忘记了 mysql_real_escape_string(),你可能永远不会发现问题,直到你受到攻击。 - Savageman
1
作为一个初学者,数据库交互、输入和显示安全性、特殊字符、注入问题等方面都是非常陡峭的学习曲线。阅读您和@Charles的帖子(以及您对其他问题的PHP回答)对我有很大帮助。感谢您的所有贡献。 - James Walker
显示剩余7条评论

2

这取决于你使用的数据类型。一般最好使用mysqli_real_escape_string,但是如果你知道不会有HTML内容,使用strip_tags将增加额外的安全性。

你也可以删除你知道不应该允许的字符。


2
对于数据库插入,您只需要使用mysql_real_escape_string(或使用参数化查询)。通常情况下,您不希望在保存数据之前更改数据,如果使用htmlentities,就会发生这种情况。当您将其再次通过htmlentities显示在网页上时,它会变成一团糟。
当您在网页的某个地方显示数据时,请使用htmlentities
有点相关的是,如果您要将提交的数据发送到电子邮件中的某个位置(例如联系表单),请务必从任何用于标题的数据中删除换行符(例如发件人姓名和电子邮件地址、主题等)。
$input = preg_replace('/\s+/', ' ', $input);

如果您不这样做,垃圾邮件机器人很快就会找到您的表格并滥用它,我已经吃过亏了。


2
使用这个:
$string = htmlspecialchars(strip_tags($_POST['example']));

或者这个:
$string = htmlentities($_POST['example'], ENT_QUOTES, 'UTF-8');

1

您可以在类似以下代码中使用mysql_real_escape_string()

$query = sprintf("SELECT * FROM users WHERE user='%s' AND password='%s'",
  mysql_real_escape_string($user),
  mysql_real_escape_string($password)
);

正如文档所述,它的目的是转义作为参数传递的字符串中的特殊字符,考虑到连接的当前字符集,以便安全地将其放置在mysql_query()中。文档还补充道:

如果要插入二进制数据,则必须使用此函数。

当您在HTML内容中输出字符串时,htmlentities()用于将某些字符转换为实体。

1

这是我目前正在练习的一种方式,

  1. 在用户发起请求时,嵌入csrf和salt tempt token,并从请求中一起验证它们。请参考这里
  2. 确保不过度依赖客户端cookies,并确保使用服务器端会话。
  3. 在任何解析数据时,请确保只接受数据类型和传输方法(如POST和GET)。
  4. 确保为您的Web应用/应用程序使用SSL。
  5. 确保还生成基于时间的会话请求以限制有意的垃圾邮件请求。
  6. 当数据被解析到服务器时,请确保验证所需的请求应该是您想要的数据方法,例如json、html等,然后进行下一步操作。
  7. 使用转义类型(例如realescapestring)来转义输入中的所有非法属性。
  8. 之后,验证用户所需的数据类型是否为干净的格式。
    例子:
    - 电子邮件:检查输入是否符合有效的电子邮件格式
    - 文本/字符串:仅检查输入是否为文本格式(字符串)
    - 数字:仅允许数字格式。
    - 等等。请参考php门户网站上的php输入验证库
    - 验证后,请使用准备好的SQL语句/PDO继续进行。
    - 完成后,请确保退出并终止连接。
    - 不要忘记在完成后清除输出值。

我相信这些基本的安全措施已经足够了,可以防止大多数黑客攻击。

对于服务器端的安全性,您可能需要在apache / htaccess中设置访问限制和机器人预防以及路由预防。除了服务器端系统的安全性外,还有很多工作要做。

您可以从htaccess apache sec级别(常见实践)中学习并获取安全副本。


1
我建议使用像GUMP这样的小型验证包: https://github.com/Wixel/GUMP 围绕这样的库构建所有基本函数,几乎不可能忘记过滤。"mysql_real_escape_string"并不是好的过滤替代方案(正如"Your Common Sense"所解释的那样),如果您忘记使用它一次,整个系统将会受到注入和其他恶意攻击的攻击。

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