Вопрос

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

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%?

Это было полезно?

Решение

Я пропущу аргумент SQL-инъекции, который слишком хорошо известен, и сосредоточусь только на аспекте параметров SQL, а не на параметрах.

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

Теперь, когда параметры имеют значение, клиент отправляет запрос типа select * из таблицы, где primary_key = @pk будет каждый раз отправлять один и тот же текст SQL . независимо от того, какое значение интересует. Затем происходит то, что описанный выше весь процесс закорочен. SQL будет искать в памяти план выполнения для необработанного, не разбранного, текста , который он получил (на основе хэш-дайджеста входных данных), и, если он найден, выполнит этот план. Это означает, что нет разбора, нет оптимизации, ничего, пакет отправляется прямо в исполнение . В системах OLTP, которые выполняют сотни и тысячи небольших запросов каждую секунду, этот быстрый путь имеет огромное значение для производительности.

Если вы отправите запрос в виде select * из таблицы, где primary_key = 1 , SQL придется хотя бы проанализировать его, чтобы понять, что находится внутри текста, поскольку текст, скорее всего, новый один, отличный от любого предыдущего пакета, который он видел (даже один символ, такой как 1 и 2 , делает весь пакет другим). Затем он будет работать с результирующим синтаксическим деревом и попытаться выполнить процесс, называемый Простая параметризация . Если запрос может быть автоматически переопаретизирован, то SQL, скорее всего, найдет кэшированный план выполнения для него из других запросов, которые ранее выполнялись с другими значениями pk, и повторно использует этот план, поэтому, по крайней мере, ваш запрос не нужно оптимизировать, и вы пропустите шаг создания фактического плана выполнения. Но ни в коем случае вы не достигли полного короткого замыкания, кратчайшего возможного пути, которого вы достигли с помощью параметризованного запроса истинного клиента.

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

Другие советы

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

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

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

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

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

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

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

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

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

Поскольку у вас есть контроль над тем, что значение является целым числом, они в значительной степени эквивалентны.Обычно я не использую первую форму, и, как правило, я также не разрешаю вторую, потому что я обычно не разрешаю доступ к таблице и требую использования хранимых процедур.И хотя вы можете выполнить SP без использования коллекции параметров, я все же рекомендую использовать SPs И параметры:

-- 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
  • Разрешить выполнение SPS на основе ролей
  • Любой динамический SQL в SPs должен быть рассмотрен как абсолютно необходимый

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top