如果我要选择一个表中的行我基本上有两个选择,要么像这样
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注入的说法,那是太众所周知的,只注重参数与非参数的SQL方面。
当您发送SQL批处理到服务器上,任何批次,它必须被解析到被理解。 像任何其他编译器,SQL编译器产生的AST从文本,然后对语法树进行操作。 最终,优化变换语法树成一个执行树,最终产生一个执行计划和实际运行。 早在1995年大约黑暗时代这就不一样了,如果该批次是一个即席查询或存储过程,但今天它使绝对没有,他们都是一样的。
现在,这里的参数做出不同的是,发送类似的查询客户端select * from table where primary_key = @pk
将正好每次发送相同的SQL文本 ,无论什么价值感兴趣的是什么发生的事就是整个过程我上述被短路。 SQL会在内存中搜索的执行计划为原料,未解析文本收到(基于哈希输入摘要),如有发现,将执行该计划。 这意味着,没有分析,没有优化,什么都没有,批直接进入执行 。 在运行小请求成百上千的每秒OLTP系统,这种快速路径使一个巨大的性能差异。
如果您发送的形式查询select * from table where primary_key = 1
则SQL必须至少解析它来了解什么是里面的文字,因为文字可能是一个新的,不同于以往任何批次它看到不同(甚至像单个字符1
对2
使得整个批次不同)。 然后,它会在导致语法树工作,并尝试这一过程称为简单参数化 。 如果查询可以自动paremeterized,那么SQL可能会发现从其他的PK值以前运行和重复使用计划的其他查询,它缓存的执行计划,那么至少你的查询不需要进行优化,你跳过产生实际的执行计划的步骤。 但绝不意味着没有你实现完全短路,你真正的客户端参数化查询实现最短的路径。
你可以看看到SQL服务器,SQL统计对象服务器的性能计数器。 计数器Auto-Param Attempts/sec
会显示很多次SQL具有转换接收到查询不带参数为自动参数之一。 可避免每一次尝试,如果你正确的参数在客户端的查询。 如果你也有大量的Failed Auto-Params/sec
更是雪上加霜,这意味着该查询将会优化和执行计划生成的整个周期。
始终使用选择2)。
第一个方面是不安全的SQL注入攻击 。
第二个是,不仅更加安全,而且也将有更好的表现,因为查询优化器有更好的机会来创造良好的查询计划,因为查询看起来一样的时候,当然期望的参数。
为什么你应该避免选择1
虽然看上去你是从得到这个值select
元素,说不定有人会伪造一个HTTP请求和后为所欲为某些领域内。 您在选项1的代码至少应该更换一些危险字符/组合(即单引号,括号等)。
你为什么要鼓励使用选项2
SQL查询引擎能够缓存的执行计划和管理在它抛出各种查询统计。 因此,有统一的查询(最好的当然会是有一个单独的存储过程)加快从长远看执行。
我还没有听到上面的参数可以被劫持用于SQL注入的任何实例。 直到我看到它,否则证明,我会考虑他们的安全。
动态SQL不应该被认为是安全的。 即使有“信任”的输入,这是一个坏习惯进入。 请注意,这包括在存储过程中的动态SQL; SQL注入可以发生有作为。
因为您可以控制该值是一个整数,他们是几乎等同的。 我一般不使用第一种形式,通常,我不允许任何第二,因为我平时不允许表的访问,需要使用存储过程。 虽然你可以做SP执行不使用参数集合,我还是建议使用SPS 和参数:
-- 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#会抛出异常。