代码之家  ›  专栏  ›  技术社区  ›  sindre j

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

  •  3
  • sindre j  · 技术社区  · 15 年前

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

    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 回复  |  直到 9 年前
        1
  •  11
  •   marc_s    9 年前

    我将跳过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 VS 2 使整个批次不同)。然后它将在结果语法树上操作,并尝试调用 Simple Parameterisation . 如果查询可以自动进行参数化,那么SQL很可能会从以前使用其他pk值运行的其他查询中找到一个缓存的执行计划,并重新使用该计划,因此至少您的查询不需要优化,您可以跳过生成实际执行计划的步骤。但是,您并没有完全实现短路,这是使用真正的客户机参数化查询实现的最短路径。

    你可以看看 SQL Server, SQL Statistics Object 服务器的性能计数器。柜台 Auto-Param Attempts/sec 每秒将显示多次SQL必须将收到的没有参数的查询转换为自动参数化查询。如果在客户机中正确地参数化查询,就可以避免每次尝试。如果你还有很多 Failed Auto-Params/sec 更糟糕的是,这意味着查询将进入优化和执行计划生成的整个周期。

        2
  •  4
  •   Johannes Rudolph    15 年前

    始终使用选项2)。

    第一个 不是 安全考虑 SQL Injection attacks .

    第二个 它不仅安全得多,而且性能也会更好,因为查询优化器有更好的机会为它创建一个好的查询计划,因为查询一直看起来都是一样的,当然需要参数。

        3
  •  3
  •   Robert Koritnik    15 年前

    你为什么要避开选项1?

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

    为什么鼓励您使用选项2

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

        4
  •  2
  •   Cylon Cat    15 年前

    我还没有听说过任何可以劫持参数进行SQL注入的例子。除非我看到它被证明是安全的,否则我会认为它们是安全的。

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

        5
  •  1
  •   Cade Roux    15 年前

    因为您可以控制该值是一个整数,所以它们几乎是等价的。我通常不使用第一个窗体,通常也不允许第二个窗体,因为我通常不允许表访问,需要使用存储过程。尽管您可以在不使用参数收集的情况下执行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
    • 在SPS中的任何动态SQL都应在绝对必要的情况下进行审查。
        6
  •  1
  •   kemiller2002    15 年前

    就我个人而言,我总是习惯使用选项2。说的是:

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