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

时间:2009-10-22 16:47:15

标签: c# sql-server security sql-injection

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

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%注射是否安全?

6 个答案:

答案 0 :(得分:11)

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

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

答案 1 :(得分:4)

始终使用选项2)。

对于SQL Injection attacks

第一个 NOT 安全。

第二个 不仅更安全,而且性能也更好,因为查询优化器有更好的机会为它创建一个好的查询计划,因为查询看起来同样一直都在期待参数。

答案 2 :(得分:3)

为什么要避免使用选项1

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

为什么鼓励您使用选项2

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

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

-- 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都应该被视为绝对必要