【问题标题】:Dynamically created SQL vs Parameters in SQL ServerSQL Server 中动态创建的 SQL 与参数
【发布时间】:2009-10-22 16:47:14
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

    我将跳过众所周知的 SQL 注入参数,只关注参数与非参数的 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 大多数答案都使用了注入参数,但我不接受这一点,除非有人可以向我展示一个有效的基于整数的注入!你的回答 Remus,很可能会让我无论如何都使用参数(即使我们的数据库没有看到数千个请求/秒)谢谢
    【解决方案2】:

    始终使用选项 2)。

    第一个对于SQL Injection attacks来说是安全的。

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

    【讨论】:

      【解决方案3】:

      为什么要避免选项 1

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

      为什么鼓励您使用选项 2

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

      【讨论】:

        【解决方案4】:

        我还没有听说过任何可以劫持参数以进行 SQL 注入的示例。在我看到它证明不是这样之前,我会认为它们是安全的。

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

        【讨论】:

          【解决方案5】:

          因为您可以控制该值是否为整数,所以它们几乎是等价的。我通常不使用第一种形式,通常也不允许使用第二种形式,因为我通常不允许表访问并且需要使用存储过程。虽然你可以在不使用参数集合的情况下执行 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 都应在绝对必要时进行审查

          【讨论】:

            【解决方案6】:

            就个人而言,我总是习惯性地使用选项 2。话虽这么说:

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

            【讨论】:

              猜你喜欢
              • 2019-09-29
              • 1970-01-01
              • 2016-08-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-06-05
              • 2013-07-22
              相关资源
              最近更新 更多