您尝试使SQL查询安全有多难?

时间:2010-03-11 13:18:48

标签: sql security authentication stored-procedures

我处于这样一种情况,即我被赋予逗号分隔的VarChar作为存储过程的输入。我想做这样的事情:

SELECT * FROM tblMyTable 
INNER JOIN /*Bunch of inner joins here*/ 
WHERE ItemID IN ($MyList);

但是,您不能将VarChar与IN语句一起使用。有两种方法可以解决这个问题:

  1. (错误的方法)在String中创建SQL查询,如下所示:

    SET $SQL = ' SELECT * FROM tblMyTable INNER JOIN /*Bunch of inner joins here*/ WHERE ItemID IN (' + $MyList + ');

    EXEC($SQL);

  2. (正确的方法)创建一个包含$MyList值的临时表,然后在初始查询中加入该表。

  3. 我的问题是:

    选项2在创建临时表时性能相对较高,但不太理想。

    虽然选项1对SQL注入攻击持开放态度,但由于我的SPROC是从经过身份验证的源调用的,因此真的重要吗?只有受信任的来源才会执行这个SPROC,所以如果他们选择加扰数据库,那就是他们的特权。

    那么,你能在多大程度上保证代码安全?

6 个答案:

答案 0 :(得分:4)

您使用的数据库是什么?在SQL Server中,您可以创建一个拆分函数,该函数可以拆分长字符串并返回一个亚秒级表。您可以像查询中的常规表一样使用表函数调用(不需要临时表)

您需要创建一个分割功能,或者如果您有一个只需使用它。这就是如何使用拆分功能:

SELECT
    *
    FROM YourTable                               y
    INNER JOIN dbo.yourSplitFunction(@Parameter) s ON y.ID=s.Value

I prefer the number table approach to split a string in TSQL但是有很多方法可以在SQL Server中拆分字符串,请参阅上一个链接,该链接解释了每个链接的PRO和CON。

要使Numbers Table方法起作用,您需要进行一次性表设置,这将创建一个包含1到10,000行的表Numbers

SELECT TOP 10000 IDENTITY(int,1,1) AS Number
    INTO Numbers
    FROM sys.objects s1
    CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)

设置Numbers表后,创建此拆分功能:

CREATE FUNCTION [dbo].[FN_ListToTable]
(
     @SplitOn  char(1)      --REQUIRED, the character to split the @List string on
    ,@List     varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN 
(

    ----------------
    --SINGLE QUERY-- --this will not return empty rows
    ----------------
    SELECT
        ListValue
        FROM (SELECT
                  LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
                  FROM (
                           SELECT @SplitOn + @List + @SplitOn AS List2
                       ) AS dt
                      INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
                  WHERE SUBSTRING(List2, number, 1) = @SplitOn
             ) dt2
        WHERE ListValue IS NOT NULL AND ListValue!=''

);
GO 

您现在可以轻松地将CSV字符串拆分为表格并加入其中:

select * from dbo.FN_ListToTable(',','1,2,3,,,4,5,6777,,,')

输出:

ListValue
-----------------------
1
2
3
4
5
6777

(6 row(s) affected)

您可以像这样使用CSV字符串,而不是必需的临时表:

SELECT * FROM tblMyTable 
INNER JOIN /*Bunch of inner joins here*/ 
WHERE ItemID IN (select ListValue from dbo.FN_ListToTable(',',$MyList));

答案 1 :(得分:1)

我个人更喜欢选项2,因为源代码经过身份验证,并不意味着您应该放松警惕。如果经过身份验证的低级别用户仍然能够针对您不想要的数据库执行命令,那么您可能会对潜在的权限升级持开放态度。

您使用“可信来源”的短语 - 如果您假设X-Files方法并且不信任任何人,那可能会更好。

答案 2 :(得分:1)

如果有人弄错了数据库,您可能仍在接听电话。

与选项2类似的好选项是使用函数从CSV列表在内存中创建表。它速度相当快,并提供选项二的保护。然后该表可以连接到内部联接,例如

CREATE FUNCTION [dbo].[simple_strlist_to_tbl] (@list nvarchar(MAX))
   RETURNS @tbl TABLE (str varchar(4000) NOT NULL) AS
BEGIN
   DECLARE @pos        int,
           @nextpos    int,
           @valuelen   int

   SELECT @pos = 0, @nextpos = 1

   WHILE @nextpos > 0
   BEGIN
      SELECT @nextpos = charindex(',', @list, @pos + 1)
      SELECT @valuelen = CASE WHEN @nextpos > 0
                              THEN @nextpos
                              ELSE len(@list) + 1
                         END - @pos - 1
      INSERT @tbl (str)
         VALUES (substring(@list, @pos + 1, @valuelen))
      SELECT @pos = @nextpos
   END
  RETURN
END

然后在加入中:

tblMyTable INNER JOIN
simple_strlist_to_tbl(@MyList) list ON tblMyTable.itemId = list.str

答案 3 :(得分:0)

选项3是在将字符串连接到SQL语句之前确认列表中的每个项目实际上是一个整数。

通过解析输入字符串(例如,拆分为数组),循环并将每个值转换为int,然后在连接回SQL语句之前自己重新创建列表来完成此操作。这将为您提供合理的保证,即不能进行SQL注入。

连接由应用程序创建的字符串更安全,因为您可以执行诸如检查int之类的操作,但这也意味着您的代码以后续开发人员可能稍微修改的方式编写,从而打开备份SQL注入的风险,因为他们没有意识到这是您的代码所保护的。如果你走这条路,一定要好好评论你在做什么。

答案 4 :(得分:0)

第三种选择:将值传递给数组中的存储过程。然后,您可以在代码中组合逗号分隔的字符串并使用动态SQL选项,或者(如果您的RDBMS允许它的风格)直接在SELECT语句中使用该数组。

答案 5 :(得分:0)

为什么不编写CLR拆分功能,这将完成所有工作的好处和简单?您可以编写用户定义的表函数,它将返回一个使用.Net infructure进行字符串拆分的表。在SQL 2008中,如果他们返回以任何方式排序的字符串,你甚至可以给他们提示......比如提升或者什么可以帮助优化器? 或许你不能做CLR集成然后你必须坚持tsql但我个人会去CLR解决方案