2010-03-11 30 views
4

我处于这样一种情况:我得到一个以逗号分隔的VarChar作为存储过程的输入。我想要做这样的事情:你会如何努力使你的SQL查询安全?

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

但是,你不能用一个varchar与IN声明。有两种方法来解决这个问题:

  1. (错误的方式)在字符串创建SQL查询,像这样:

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

    EXEC($SQL);

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

我的问题是:

选项2与创建临时表,这是不理想的一个比较大的性能损失。

虽然选项1对SQL注入攻击是开放的,但是由于我的SPROC是从已认证的源中调用的,因此真的是很重要吗?只有可信赖的来源才会执行此SPROC,因此如果他们选择滥用数据库,那就是他们的特权。

那么,你会走多远让你的代码安全?

+0

什么是ItemID?如果它是数字,生成一个适当的字符串是微不足道的。如果是字符串,请准备一些愚蠢的字符串,如“no'id”(请注意') – TomTom 2010-03-11 13:23:54

+0

我将通过使用参数化查询确保所有查询都100%安全。 – rook 2010-03-11 16:20:44

回答

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中的字符串,见前面的链接,这说明各的优点和缺点。

对于数字表的方法来工作,你需要做的这一次表的设置,这将创建一个包含从1到10000行的表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,,,') 

OUTPUT:

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

我个人更喜欢选项2,因为来源是经过认证的,并不意味着你应该放松警惕。如果经过身份验证的低级用户仍然可以对尚未预期的数据库执行命令,则可以开放潜在的权限升级。

您使用“可信来源”的短语 - 如果您承担X档案的处理并且不信任任何人,这可能会更好。

+1

“不信任任何人” - 甚至不在StackOverflow上;) – APC 2010-03-11 13:41:46

0

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

通过解析输入字符串(例如,拆分为数组),循环并将每个值转换为int来完成此操作,然后在连接回SQL语句之前自行重新创建列表。这将使您确信SQL注入不会发生。

连接已由应用程序创建的字符串比较安全,因为您可以执行诸如检查int之类的操作,但这也意味着您的代码是以后续开发人员稍微修改的方式编写的,因此会打开回来增加了SQL注入的风险,因为他们没有意识到这是你的代码所抵御的。如果你走这条路线,确保你评论你正在做什么。

0

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

1

如果有人窃听数据库,您可能仍然在接听电话。

类似于选项二的一个很好的选择是使用函数在CSV列表中创建一个内存表。它速度相当快,并提供了选项二的保护。然后,该表可以连接到Inner Join,例如

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 
0

你为什么不写一个CLR分割功能,这将做所有的工作好,易于?您可以编写用户定义的表函数,这些函数将返回一个使用.net infructure进行字符串拆分的表。地狱在SQL 2008中,你甚至可以给他们提示,如果他们返回以任何方式排序的字符串......就像升序或可以帮助优化器的东西? 或者也许你不能做CLR集成那么你必须坚持tsql,但我个人会去CLR解决方案