在SQL Server中动态创建SQL vs参数

如果我要从表中选择一行,我基本上有两个选项,或者像这样

int key = some_number_derived_from_a_dropdown_or_whatever SqlCommand cmd = new SqlCommand("select * from table where primary_key = " + key.ToString()); 

或使用参数

 SqlCommand cmd = new SqlCommand("select * from table where primary_key = @pk"); SqlParameter param = new SqlParameter(); param.ParameterName = "@pk"; param.Value = some_number_derived_from_a_dropdown_or_whatever; cmd.Parameters.Add(param); 

现在,我知道第一种方法因为可能的sql注入攻击而不受欢迎,但在这种情况下,参数是一个整数,因此不应该真正注入恶意代码。

我的问题是:您是否在生产代码中使用选项1,因为您认为使用安全是因为易于使用和控制插入的参数(如上所述,或者参数是否在代码中创建)? 或者你总是使用参数,不管是什么? 参数100%注射安全吗?

我将跳过SQL Injection参数,这是众所周知的,只关注参数与非参数的SQL方面。

当您将SQL批处理发送到服务器时,任何批处理都必须解析它才能被理解。 与任何其他编译器一样,SQL编译器必须从文本生成AST ,然后在语法树上操作。 最终,优化器将语法树转换为执行树,最终生成执行计划并实际运行。 回到大约1995年的黑暗时代,如果批处理是Ad-Hoc查询或存储过程,它会产生不同,但今天它绝对没有,它们都是相同的。

现在参数有所不同的是,发送查询的客户端如select * from table where primary_key = @pk都会发送完全相同的SQL文本 ,无论感兴趣的是什么值。然后会发生什么呢? 整个过程我上面描述的是短路的。 SQL将在内存中搜索它收到的原始未解析文本的执行计划(基于输入的哈希摘要),如果找到,将执行该计划。 这意味着没有解析,没有优化,没有,批处理直接执行 。 在每秒运行数百和数千个小请求的OLTP系统上,这种快速路径会产生巨大的性能差异。

如果以表格select * from table where primary_key = 1发送查询, select * from table where primary_key = 1则SQL必须至少解析它以了解文本内部的内容,因为文本可能是新文本,与之前看到的任何批次不同(即使像12这样的单个字符也会使整个批次不同。 然后它将对生成的语法树进行操作,并尝试一个称为简单参数化的过程。 如果查询可以自动进行计数,那么SQL可能会从之前使用其他pk值运行的其他查询中找到缓存的执行计划,并重用该计划,因此至少您的查询不需要优化,并且您跳过了生成实际执行计划的步骤。 但是,通过真正的客户端参数化查询,您无法实现完全短路,最短路径。

您可以查看服务器的SQL Server,SQL Statistics对象性能计数器。 计数器Auto-Param Attempts/secAuto-Param Attempts/sec显示多次SQL必须将不带参数的查询转换为自动参数化查询。 如果在客户端中正确参数化查询,则可以避免每次尝试。 如果你还有很多Failed Auto-Params/sec甚至更糟,那就意味着查询正在进行优化和执行计划生成的整个周期。

始终使用选项2)。

对于SQL注入攻击 ,第一个是不安全的。

第二个不仅更安全,而且还会表现更好,因为查询优化器有更好的机会为它创建一个好的查询计划,因为查询看起来始终相同,当然需要参数。

为什么要避免选项1

即使您似乎从select元素获取此值,但有人可能伪造HTTP请求并在某些字段中发布他们想要的任何内容。 选项1中的代码至少应该替换一些危险的字符/组合(即单引号,方括号等)。

为什么鼓励您使用选项2

SQL查询引擎能够缓存执行计划并管理为其抛出的各种查询的统计信息。 因此,统一查询(当然最好的是具有单独的存储过程)可以加快执行的速度。

我还没有听说过任何可以为SQL注入劫持参数的例子。 直到我看到它certificate不然,我认为它们是安全的。

永远不应该认为动态SQL是安全的。 即使有“可信”的输入,也是一个不好的习惯。 请注意,这包括存储过程中的动态SQL; SQL注入也可以在那里发生。

因为您可以控制该值是整数,所以它们几乎相同。 我通常不使用第一种forms,通常,我也不允许第二种forms,因为我通常不允许表访问并需要使用存储过程。 虽然你可以在不使用参数集合的情况下执行SP,但我仍然建议使用SP 参数:

 -- Not vulnerable to injection as long as you trust int and int.ToString() int key = some_number_derived_from_a_dropdown_or_whatever ; SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString()); -- Vulnerable to injection all of a sudden string key = some_number_derived_from_a_dropdown_or_whatever ; SqlCommand cmd = new SqlCommand("EXEC sp_to_retrieve_row " + key.ToString()); 

请注意,虽然选项1 在您的情况下是安全的但当有人看到并使用非整数变量的技术时会发生什么 – 现在您正在训练人们使用可以开放注射的技术。

请注意,即使应用程序代码有问题,您也可以主动强化数据库以避免注入生效。 对于应用程序使用的帐户/角色:

  • 仅允许绝对必要的访问表
  • 仅允许访问视图,绝对必要
  • 不允许DDL语句
  • 允许以角色为基础执行SP
  • SP中的任何动态SQL都应该被视为绝对必要

就个人而言,我总是习惯使用选项2。 话虽如此:

由于您强制从下拉值转换为int,这将提供一些防止sql注入攻击的保护。 有人试图在发布的信息中注入额外的信息,C#在尝试将恶意代码转换为阻止尝试的整数值时会抛出exception。