Насколько усердно вы пытаетесь обезопасить свои SQL-запросы? - PullRequest
4 голосов
/ 11 марта 2010

Я нахожусь в ситуации, когда мне дают запятую VarChar в качестве входных данных для хранимой процедуры. Я хочу сделать что-то вроде этого:

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, поэтому, если они решат увеличить базу данных, это является их прерогативой.

Итак, как далеко вы зайдете, чтобы обезопасить свой код?

Ответы [ 6 ]

4 голосов
/ 11 марта 2010

Какую базу данных вы используете? в SQL Server вы можете создать функцию разбиения, которая может разбивать длинную строку и возвращать таблицу за секунду. Вы используете вызов табличной функции как обычная таблица в запросе (временная таблица не требуется)

Вам нужно создать функцию разделения, или, если она у вас есть, просто использовать ее. Вот как можно использовать функцию разделения:

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

Я предпочитаю подход с использованием таблиц чисел для разделения строки в TSQL , но существует множество способов разделения строк в SQL Server, см. Предыдущую ссылку, которая объясняет за и против каждого из них.

Чтобы метод таблицы чисел сработал, необходимо выполнить настройку единой таблицы, которая создаст таблицу Numbers, содержащую строки от 1 до 10000:

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 голос
/ 11 марта 2010

Если кто-то запугивает базу данных, возможно, вам все еще звонят.

Хорошим вариантом, аналогичным второму варианту, является использование функции для создания таблицы в памяти из списка 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
1 голос
/ 11 марта 2010

Я бы лично предпочел вариант 2 в том, что если источник аутентифицирован, это не значит, что вы должны ослабить свою охрану. Вы бы оставались открытыми для потенциальной эскалации прав, когда аутентифицированный пользователь с низким уровнем доступа по-прежнему может выполнять команды для базы данных, которую вы не планировали.

Фраза, которую вы используете «доверенные источники» - может быть, лучше, если вы примете участие в «Секретных материалах» и никому не будете доверять.

0 голосов
/ 11 марта 2010

Почему бы вам не написать функцию разделения CLR, которая сделает всю работу красивой и легкой? Вы можете написать пользовательские табличные функции, которые будут возвращать таблицу, делающую строки с помощью .Net infructure. Черт возьми, в SQL 2008 вы можете даже дать им подсказки, если они возвращают отсортированные строки каким-либо образом ... как по возрастанию или что-то, что может помочь оптимизатору? Или, может быть, Вы не можете сделать интеграцию CLR, тогда Вы должны придерживаться tsql, но я лично выбрал бы CLR soluton

0 голосов
/ 11 марта 2010

Третий вариант: передать значения хранимой процедуре в массиве. Затем вы можете либо собрать строку с разделителями-запятыми в своем коде и использовать параметр динамического SQL, либо (если это допускает ваша разновидность RDBMS) использовать массив непосредственно в операторе SELECT.

0 голосов
/ 11 марта 2010

Вариант 3 состоит в том, чтобы подтвердить, что каждый элемент в списке на самом деле является целым числом, прежде чем объединять строку с вашим оператором SQL.

Сделайте это, проанализировав входную строку (например, разделив ее на массив), пройдя по циклу и преобразовав каждое значение в целое число, а затем пересоздайте список самостоятельно перед конкатенацией обратно в оператор SQL. Это даст вам разумную уверенность в том, что SQL-инъекция невозможна.

Безопаснее объединять строки, созданные вашим приложением, потому что вы можете выполнять такие вещи, как проверка на int, но это также означает, что ваш код написан так, что последующий разработчик может немного изменить его, открыв, таким образом, обратно риск внедрения SQL-кода, потому что они не понимают, от чего защищает ваш код. Если вы идете по этому пути, обязательно прокомментируйте, что вы делаете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...