FromSql(): построение строки запроса из списка строк для предотвращения внедрения.

Я создаю поиск SQL в EF Core. Microsoft рекомендует не объединять строки, поскольку это делает приложение уязвимым для SQL-инъекций, как подробно описано в документации Microsoft: https://docs.microsoft.com/en-us/ef/core/querying/raw-sql..

Всегда используйте параметризацию для необработанных SQL-запросов: в дополнение к проверке пользовательского ввода всегда используйте параметризацию для любых значений, используемых в необработанном SQL-запросе/команде. API-интерфейсы, которые принимают необработанную строку SQL, например FromSql и ExecuteSqlCommand, позволяют легко передавать значения в качестве параметров. Перегрузки FromSql и ExecuteSqlCommand, которые принимают FormattableString, также позволяют использовать синтаксис интерполяции строк таким образом, который помогает защититься от атак путем внедрения кода SQL.

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

Информация, хранящаяся в этой базе данных, не является конфиденциальной, но, очевидно, я не хотел бы оставлять базу данных уязвимой.

У меня есть параметр List<string> searchTerms, который мне нужно перебрать и построить запрос на основе этого списка.

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

var query = String.Format("SELECT ... where MySqlField like '%{0}%'", searchTerm[0]);

for (int i = 1; i < searchTerm.Count(); i++)
{
    query += String.Format(" and MySqlField like '%{0}%'", searchTerm[i]);
}

var results = context.MySqlTable.FromSql(query);

Несмотря на то, что я использую интерполяцию, будет ли здесь достаточно дополнительной проверки? Я что-то упустил?

Есть ли запрос linq, который может сделать то же самое со списком?


person lloyd    schedule 15.04.2019    source источник
comment
FromSql имеет перегрузку, позволяющую передавать параметры. Почему бы вам не использовать его?   -  person Steve    schedule 15.04.2019
comment
Кстати, несколько условий И на одном и том же поле. Вероятно, вам нужно использовать ИЛИ здесь.   -  person Steve    schedule 15.04.2019
comment
@Steve Как мне добавить параметры в перегрузку FromSql на основе списка изменяющегося размера?   -  person lloyd    schedule 15.04.2019
comment
Если вы передаете несколько значений, рассматривали ли вы возможность использования табличного параметра? Если вы предпочитаете не использовать TVP, вы можете передать список с разделителями и использовать разделитель строк, например STRING_SPLIT.   -  person Larnu    schedule 15.04.2019


Ответы (4)


Ваш код должен быть достаточно хорош с небольшой модификацией:

var query = String.Format("SELECT ... where 1=1 ");

for (int i = 0; i < searchTerm.Count(); i++)
{

    query += $" and MySqlField like '%'+{{{i}}}+'%'";
}

var results = context.MySqlTable.FromSql(query, searchTerm.ToArray());
person alans    schedule 15.04.2019
comment
Это именно то, что я ищу с точки зрения использования списка/массива в качестве строкового параметра. Можете ли вы объяснить where 1=1? - person lloyd; 15.04.2019
comment
@ Это 1=1 является фиктивным критерием, поэтому я могу начать добавлять and ... с начала цикла. - person alans; 15.04.2019

Вариантов немного:

  1. Передайте свои значения в XML (или JSON, если используете более новый SQL Server), а затем напишите статический запрос XML/JSON.

  2. создайте временную таблицу, вставьте все значения поиска во временную таблицу, а затем выполните статический запрос.

person Piotr    schedule 15.04.2019
comment
Номер 2, безусловно, является правильным ответом на эту проблему. - person Hogan; 15.04.2019
comment
Вариант 3. Используйте полнотекстовый поиск. - person alans; 15.04.2019

Я не могу проверить это в данный момент, поэтому я сообщаю вам об этом подходе.

List<string> ph = new List<string>();
int count = 0;
foreach(string s in searchTerm)
{
    ph.Add($"MySqlField LIKE '%{{{count}}}%'");
    count++;
}

if(count > 0)
    query = query + " WHERE " + string.Join(" OR ", ph);
var results = context.MySqlTable.FromSql(query, searchTerm.ToArray());

И хотя это похоже на подход с конкатенацией строк, мы можем прочитать в документы

Хотя это может выглядеть как синтаксис String.Format, предоставленное значение заключено в параметр, а сгенерированное имя параметра вставлено туда, где был указан заполнитель {0}.

person Steve    schedule 15.04.2019
comment
Не нужно тестировать, я только ищу подход. Могу протестировать на своем. - person lloyd; 15.04.2019
comment
Вероятно, есть что-то, что нужно исправить в подстановочном знаке% и одинарных кавычках. Теперь похоже, что здесь мы строим литеральный текст вместо заполнителя параметра. - person Steve; 15.04.2019

Я бы рекомендовал изучить LINQ, если вы используете EF. Использование необработанной строки sql не очень хорошая идея как для безопасности, так и для производительности. LINQ предоставляет надежный способ выполнения запросов. Просто будьте осторожны с тем, как вы пишете свои запросы, так как LINQ может попытаться их усложнить.

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/

РЕДАКТИРОВАТЬ:

Я пропустил последнюю строчку вашего поста. К сожалению, следующее должно помочь вам на правильном пути:

for (var item in searchTerms) {
    query = query.Where(w => w.MySqlField.Contains(item.Value));
}
person Triston Wasik    schedule 15.04.2019
comment
Я бы сделал это с самого начала, но я хотел избежать сценария, когда я выполняю поиск через несколько итераций, и в этот момент мне нужно было бы убедиться, что результаты различны - это похоже на дополнительные ненужные накладные расходы. Я хотел бы выполнить один запрос, если есть какой-либо возможный способ. - person lloyd; 15.04.2019
comment
Без дополнительных вложений в ваш код единственная помощь, которую я могу вам предложить, будет заключаться в том, чтобы сохранить запрос как queryable перед запуском этого foreach (я понимаю, что забыл каждый). Он не будет обрабатывать запрос, пока вы его не преобразуете или не заставите с помощью .ToList() или любого другого предпочитаемого вами метода. Таким образом, вы попадете в базу данных только один раз. Чтобы убедиться, что входные данные вашего запроса различны, вы можете использовать searchTerms.Distinct() с небольшими фактическими затратами. Хотя вы можете посмотреть на свои входные данные, чтобы понять, почему у вас будет несколько. - person Triston Wasik; 15.04.2019