更快的 SQL 注入预防

信息安全 php sql注入 mysql
2021-08-30 01:05:15

我环顾四周,找不到我的问题的答案。

我将最新的 PHP 用于服务器端脚本,将 MySQL 用于我的数据库。字符集utf8mb4是否有所作为。

到目前为止,我一直使用准备好的语句来保护自己免受 SQL 注入。但是,由于我的大多数查询只执行一次,所以代码真的很当我$conn->query()改为使用$conn->real_escape_string()所有变量时,它会更快。

但是,$conn->real_escape_string()也很慢,因为每次通话都有一个往返。但我不明白为什么需要往返,因为我觉得转义函数可以用 PHP 编程。

从文档中,函数看起来像这样:

编码的字符为 NUL (ASCII 0)、\n、\r、\、'、" 和 Control-Z。

function sanitize($str)
{
    str_replace(array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $str);
}

假设我确保它采用正确的编码并且它不是空的。

这在其中一个文档中作为评论发布。顺便说一句,str_replace适用于 Unicode。

但是,我想到了一些更有意义的东西。看看这个页面,我可以进一步扩展。对于这样的声明:

$sql = "UPDATE ipsum SET lorem=$str WHERE id=1337";

我不能这样做(我正在事先检查以确保它是 UTF-8):

function sanitize($str)
{
    return "'" .str_replace(array("\\", "'"), array("\\\\", "\\'"), $str) . "'";
}

这将防止 SQL 注入?为什么不?我想不出它会失败的输入。

如果没有,是否有更好的 PHP 函数可以更好地防止 SQL 注入?

提前致谢!您可以在此处测试 SQL


更新:收到了我预期的回复。澄清一下,我并不是要你发表你的经典讲道“准备好的陈述是最好的事情,否则就死了”。如果我想要,我会在 Yahoo! 上询问。答案不在这里。

我想知道为什么我的方法(第二种)不安全(或者如果是)。最好包括讨论什么real_escape_string或准备好的语句实际上对数据进行了清理。MySQL 和 PHP 都是开源的,我相信有人知道调用这些函数时会发生什么。

我根本不明白为什么需要往返来清理字符串。像这样的方法怎么不能在 PHP 中实现?


更新 2:我已经遍历了所有的 unicode 字符(0x0000 到 0x1F77F,在 Wikipedia 上找到了这个)并注意到,如果real_escape_string()只转义单个字符而不是短语(根据文档它确实如此),在utf8mb4字符集下,改变的字符是:

Unicode 0 => \0

unicode 10 =>\n

unicode 13 =>\r

unicode 26 =>\Z

Unicode 34 =>\"

unicode 39 =>\'

unicode 92 =>\\

因此,即使它是utf8mb4,它也与文档所说的没有什么不同(猜测是因为 UTF-8 是标准的)。那么为什么这不能在 PHP 中实现呢?

这是一个PHP 脚本,它结合了所有这些 un​​icode 并对其进行清理。这是一个SQL fiddle

4个回答

关于您的清理功能-虽然我无法为您提供的确切示例生成漏洞利用,但如果我们将您的示例更改为以下内容:

$sql = "UPDATE ipsum SET price=$str WHERE id=1337";

如果一个值10, otherColumn=1234或可能10;--被传递给$str你,你可能会看到问题。如果您推出自己的清理功能并开始在您的代码中使用它,那么出现这样的漏洞似乎只是时间问题。我会继续玩,看看是否可以找到您在问题中提供的确切示例的漏洞利用。

也就是说,我不建议您推出自己的消毒功能。比您和我更聪明的人已经付出了相当大的努力来测试当前接受的使用准备好的语句来防止 SQL 注入的行业标准的有效性,因此您可以(相当)确定它是安全的。自己动手几乎总是一个错误。

另外 - 我敢打赌,有些人在处理更多流量并需要比当前项目更高性能的环境中使用准备好的语句。

如果您在访问数据层时遇到性能问题,我建议您花一些时间查看性能调整,而不是构建自己的清理功能。

对数据层的性能调优访问是开发中必不可少且正常的部分。滚动你自己的消毒不是。

首先,PHP 已经有了你的第二个函数,它被称为addlashes()

文档明确说:

为了对数据库参数进行转义,出于安全原因,应使用 DBMS 特定的转义函数(例如 MySQL 的 mysqli_real_escape_string() 或 PostgreSQL 的 pg_escape_literal()、pg_escape_string())。

如果您的 DBMS 没有转义函数并且 DBMS 使用 \ 来转义特殊字符,则只有当此转义方法适合您的数据库时,您才能使用此函数。请注意,使用 addlashes() 进行数据库参数转义可能会导致大多数数据库出现安全问题。

您的第一个函数还缺少几个对 MySQL 语法很重要的字符。

您不应该诉诸于此的其他重要原因:

  • 假设您可以对特定查询进行一些更有效但不太全面的清理工作。您是否想尝试并充分考虑针对您编写的每个查询的所有潜在攻击向量?多久才能犯错?

  • 在 mysql_real_escape_string 上使用准备好的语句的一个很好的理由是,有一天你将不可避免地忘记转义某些东西。众所周知的 PHP 项目中的许多 SQL 注入都是由于某些人忘记逃避某些东西造成的,这种情况发生在每个人身上。如果您忘记调用 bindParam() 至少使用准备好的语句,您的查询将不起作用。

如果您想提高性能,请实施缓存并完全避免 MySQL 查询。

另外,请确保您使用的是最新版本的 MySQL:

在 5.1.17 之前,查询缓存不用于预准备语句。 http://dev.mysql.com/doc/refman/5.1/en/query-cache.html

在安全方面不要自己动手。

您可以卸下汽车的制动器以减轻重量吗?好吧,如果你只在一个孤立的地方开车保持最低速度确保不会发生任何异常情况(比如一个不知情的人使用汽车),可能。

只要您控制系统的每个方面并且不忽略任何事情,您的方法就“有效”。你已经达不到第二个要求(你不知道 NO_BACKSLASH_ESCAPE设置),那么是什么让你认为现在一切都很好?你确定你没有忽略另一个设置吗?你能保证现在和将来在系统上工作的任何人都知道奇怪的限制并且永远不会犯错吗?

就个人而言,我不会把钱押在上面。当我们为现实世界编写应用程序时,我们希望它们能够在所有条件下工作,即使我们不能控制每一个设置,即使有人犯了错误,即使我们忽略了一些细节。换句话说,解决方案应该是健壮的,因为数十年的软件开发表明,人类实际上是容易犯错的。准备好的语句非常健壮,mysql_real_escape_string()也相当健壮。你的功能不是。完全没有。

我还想知道您是如何得出这样的结论的:既准备好的陈述又mysql_real_escape_string()“太慢了”。如果您有这么多用户,您需要担心一些额外的往返,那么您最不想要的就是一些自制的安全工具。如果您没有那么多用户,那么我认为您只是在夸大其词。

您的代码失败:

  • 如果 MySQL 服务器上的默认连接字符集不是严格的 ASCII 超集,例如,如果它是 Shift-JIS(它可以在多字节序列的第二个字节中走私字节作为反斜杠);

  • 如果 MySQL 服务器配置为使用 no_backslash_escape sql_mode 选项(因为它可能是为了互操作性,因为反斜杠转义是非 ANSI 标准的)。

在这两种情况下,都有输入可以逃避具有潜在安全灾难后果的字符串文字。

因此,对于您目前的设置来说可能没问题,但是对于其他任何人来说,在其他任何地方运行都非常脆弱。