我知道准备好的语句用于避免 SQL 注入。只是想知道准备好的语句的机制/原理是如何工作的?不同的编程语言有区别吗?它如何检测哪一部分应该是功能性的,哪一部分是用户输入的字段?
2 回答
这与安全性无关,而与效率有关:安全性是一种奖励。您当然意识到查询必须由服务器解析,以便将它们转换为有意义的指令以执行它们。这与必须将源代码编译成机器代码的常规程序的情况相同,但编译工作由 DB 服务器完成。
每次服务器接收到 SQL 查询时,它几乎都必须完成这项工作,除非有办法告诉它记住必须多次执行的查询。准备查询实际上是在告诉服务器。
第二个想法是允许对查询进行参数化,因为程序通常需要多次运行查询,而从一次迭代到下一次迭代只有几个值发生变化。参数使这个过程很容易与缓存查询准备相结合。
至于这整个过程是如何工作的,如前所述,这只是编译。SQL 被解析,转换成抽象语法树,在所述树上应用许多转换以产生更优化的版本,或者通过消除无用的子句,或者以更好地利用目标模式结构的方式重新制定它们。然后将生成的树转换为每次EXECUTE
发出命令时服务器将执行的指令流。根据 SQL 查询中参数的放置位置,编译步骤可能会在初始解析后延迟,因为值可能会指示应如何进行优化。
注入无法对准备好的查询起作用的主要原因是注入依赖于 SQL 语法,但是在准备好语句之后,即。在注入数据真正提交到服务器的时候,已经完成了SQL解析,所以不会再出现语法问题。参数按“原样”获取,在周围的查询中没有任何形式的语法解释:它们仅被强制转换为出现其关联绑定的表达式所需的类型。
例如,经典的注入方法是缩短语句,插入一些其他命令,然后添加一个最终命令,以确保尾随的 SQL 代码不会引发语法错误。
SELECT * FROM table WHERE x = ? AND k = 1
如果在上面的查询中,我们将问号替换为:0; DROP TABLE table
,我们实现了一次注入。该语句的含义已偏离其最初意图,现在它将执行不需要的代码。但是,尾随AND k = 1
会产生语法错误,因此我们必须附加另一个命令,该命令将在语法上更正整个字符串,例如; SELECT 0 FROM table WHERE 1 = 1
.
只有在解析之前发生占位符替换时,这种注入才可能起作用。否则,绑定到的整个字符串?
将只是一个没有其他语法意义的字符串。这是对另一种类型(int
,enum
等)的强制,但可能会产生错误。
关于该主题的维基百科页面在涵盖它方面做得比这个不起眼的答案要好得多,有许多示例和参考资料,请不要犹豫,查阅它以获得更好的理解。
它取决于您用来访问数据的库。该库用于定义特殊的转义语法来定义查询的哪一部分将被替换,哪一部分是静态的。像这样:
string commandText = "UPDATE Customers SET Active = 1 WHERE CustomerID = @ID;";
SqlCommand command = new SqlCommand(commandText, connection);
command.Parameters.Add("@ID", SqlDbType.Int);
command.Parameters["@ID"].Value = customerID;
这是使用 ADO.NET 数据访问库的 C# 代码。它基本上可以用实际参数值替换运行时中的@ID。好吧,实际上它更复杂,因为它也进行缓存,因此它将尚未扩展的字符串发送到服务器,但我们现在可以忘记这个缓存部分。