小编典典

准备好的语句如何防止 SQL 注入攻击?

all

准备好的语句如何帮助我们防止SQL
注入
攻击?

维基百科说:

准备好的语句对 SQL 注入具有弹性,因为稍后使用不同协议传输的参数值不需要正确转义。如果原始语句模板不是来自外部输入,则不会发生 SQL 注入。

我不能很好地看到原因。什么是简单的英语和一些例子的简单解释?


阅读 60

收藏
2022-06-22

共1个答案

小编典典

这个想法很简单——查询和数据分别发送到数据库 服务器
就这样。

SQL 注入问题的根源在于 代码和数据的混合。

事实上,我们的 SQL 查询是 一个合法的程序 。我们正在动态创建这样一个程序,动态添加一些数据。因此,数据可能会干扰 程序代码
甚至改变它,正如每个 SQL 注入示例所显示的那样(PHP/Mysql 中的所有示例):

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";

将产生一个常规查询

SELECT * FROM users where id=1

而这段代码

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";

会产生恶意序列

SELECT * FROM users where id=1; DROP TABLE users;

它之所以有效,是因为我们将数据直接添加到程序主体中,并且它成为程序的一部分,因此数据可能会改变程序,并且根据传递的数据,我们将有常规输出或users删除表。

虽然 在准备好的语句的情况下,我们不会改变我们的程序,但它保持不变
这就是重点。

我们首先向服务器发送一个 程序

$db->prepare("SELECT * FROM users where id=?");

其中数据被称为参数或占位符的某个 变量替换。

请注意,将完全相同的查询发送到服务器,其中没有任何数据!然后我们使用 第二个 请求发送数据,基本上与查询本身分开:

$db->execute($data);

所以它不能改变我们的程序并造成任何伤害。
很简单——不是吗?

我唯一要补充的是,在每本手册中总是省略:

准备好的语句只能保护 数据文字 ,但不能与任何其他查询部分一起使用。
所以,一旦我们必须添加一个动态 标识符 ——例如一个字段名——准备好的语句就帮不了我们了。我最近已经解释了这件事7,所以我不会重复自己:

这里的每个答案都只涵盖了问题的一部分。事实上,我们可以动态地将四个不同的查询部分添加到 SQL 中:-

  • a string
  • a number
  • an identifier
  • a syntax keyword

准备好的陈述只涵盖其中两个。

但有时我们必须使查询更加动态,同时添加运算符或标识符。因此,我们将需要不同的保护技术。

一般来说,这种保护方法是基于白名单的。

在这种情况下,每个动态参数都应该在您的脚本中进行硬编码并从该集合中选择。例如,要进行动态排序:

$orders  = array("name", "price", "qty"); // Field names
$key = array_search($_GET['sort'], $orders)); // if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. 
$query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

为了简化这个过程,我编写了一个白名单辅助函数,它可以在一行中完成所有工作:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

还有另一种保护标识符的方法 - 转义,但我宁愿坚持将白名单作为一种更强大和明确的方法。然而,只要您引用了标识符,您就可以转义引号字符以使其安全。例如,默认情况下,对于 mysql,您必须将引号字符加倍才能对其进行转义。对于其他其他 DBMS 转义规则会有所不同。

尽管如此,SQL 语法关键字(例如AND,DESC等)仍然存在问题,但在这种情况下,白名单似乎是唯一的方法。

因此,一般建议可以表述为

  • 任何表示 SQL 数据文字的变量(或者,简单地说 - SQL 字符串或数字)都必须通过准备好的语句添加。没有例外。
  • 任何其他查询部分,例如 SQL 关键字、表或字段名称或运算符 - 必须通过白名单进行过滤。

更新

尽管关于 SQL 注入保护的最佳实践达成了普遍共识,但仍然存在许多不好的实践。其中一些在 PHP 用户的脑海中根深蒂固。例如,在这个页面上(尽管大多数访问者看不到)超过 80 个已删除的答案- 由于质量差或宣传不良和过时的做法,所有答案都被社区删除。更糟糕的是,一些不好的答案并没有被删除,而是蓬勃发展。

或者有一个稍微好一点的答案,它暗示了另一种字符串格式化方法,甚至把它吹捧为终极灵丹妙药。当然,事实并非如此。这种方法并不比常规字符串格式化好,但它保留了所有缺点:它仅适用于字符串,并且与任何其他手动格式化一样,它本质上是可选的、非强制性的措施,容易出现任何类型的人为错误。

我认为这一切都是因为一个非常古老的迷信,得到了OWASPPHP 手册等权威机构的支持,它宣称任何“转义”和 SQL 注入保护之间都是平等的。

不管 PHP 手册多年来说什么,\*_escape_string绝不会保证数据安全,也从来没有打算这样做。除了对字符串以外的任何 SQL 部分无用之外,手动转义也是错误的,因为它是手动的,与自动相反。

OWASP 让情况变得更糟,强调逃避用户输入,这完全是胡说八道:在注入保护的上下文中不应该有这样的词。每个变量都有潜在的危险 - 无论来源如何!或者,换句话说 - 每个变量都必须正确格式化才能放入查询中 - 无论来源如何。重要的是目的地。开发人员开始将绵羊与山羊分开的那一刻(考虑某个特定变量是否“安全”)他/她迈出了走向灾难的第一步。更不用说,甚至措辞都暗示在入口点进行批量转义,类似于非常神奇的引号功能 - 已经被鄙视、弃用和删除。

因此,与任何“转义”不同,准备好的语句确实防止 SQL 注入的措施(如果适用)。

2022-06-22