Динамически создаваемый SQL и параметры в SQL Server - PullRequest
3 голосов
/ 22 октября 2009

Если бы я выбрал строку из таблицы, у меня в основном было бы два варианта, например:

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 ]

11 голосов
/ 22 октября 2009

Я пропущу аргумент 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 против 2, делает весь пакет другим). Затем он будет работать с результирующим синтаксическим деревом и попытаться выполнить процесс под названием Simple Parameterisation . Если запрос может быть автоматически переопаретизирован, то SQL, скорее всего, найдет кэшированный план выполнения для него из других запросов, которые ранее выполнялись с другими значениями pk, и повторно использует этот план, поэтому, по крайней мере, ваш запрос не нужно оптимизировать, и вы пропустите шаг создания фактического плана выполнения. Но ни в коем случае вы не достигли полного короткого замыкания, кратчайшего возможного пути, которого вы достигли с помощью параметризованного запроса истинного клиента.

Вы можете посмотреть SQL Server, объект статистики SQL счетчик производительности вашего сервера. Счетчик Auto-Param Attempts/sec будет показывать много раз в секунду, что SQL должен преобразовать полученный запрос без параметров в автоматически параметризованный. Любой попытки можно избежать, если вы правильно параметризуете запрос в клиенте. Если у вас также большое число Failed Auto-Params/sec, что еще хуже, это означает, что запросы проходят полный цикл оптимизации и генерации плана выполнения.

4 голосов
/ 22 октября 2009

Всегда используйте вариант 2).

Первый является НЕ безопасным в отношении SQL-инъекций .

Второй не только намного безопаснее, но и будет работать лучше, потому что оптимизатор запросов имеет больше шансов создать для него хороший план запроса, потому что запрос выглядит одинаково все время ожидайте параметры конечно.

3 голосов
/ 22 октября 2009

Почему вы должны избегать Вариант 1

Даже если кажется, что вы получаете это значение из элемента select, кто-то может подделать HTTP-запрос и опубликовать все, что он хочет, в определенных полях. Ваш код в варианте 1 должен хотя бы заменить некоторые опасные символы / комбинации (например, одинарные кавычки, квадратные скобки и т. Д.).

Почему вам рекомендуется использовать вариант 2

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

2 голосов
/ 22 октября 2009

Мне еще не приходилось слышать ни одного примера, где параметры могут быть перехвачены для внедрения SQL. Пока я не увижу, что доказано обратное, я буду считать их безопасными.

Динамический SQL никогда не следует считать безопасным. Даже с «доверенным» вкладом это плохая привычка. Обратите внимание, что это включает динамический SQL в хранимых процедурах; Там также может произойти SQL-инъекция.

1 голос
/ 22 октября 2009

Поскольку у вас есть контроль, что значение является целым числом, они в значительной степени эквивалентны. Я обычно не использую первую форму, и, как правило, я также не разрешаю вторую, потому что я обычно не разрешаю доступ к таблице и требую использования хранимых процедур. И хотя вы можете выполнять 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 на основе ролей
  • Любой динамический SQL в SP должен рассматриваться как абсолютно необходимый
1 голос
/ 22 октября 2009

Лично я бы всегда использовал вариант 2 по привычке. Как говорится:

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

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