Параметризуйте
Самое главное правило — данные, присылаемые пользователем, не должны участвовать в формировании текста SQL-запроса, во всяком случае напрямую. Достигается это использованием параметризированных (подготовленных) запросов.
Так, вместо подстановки (конкатенации) пользовательского ввода, надо использовать параметры, например, в случае C# вместо фрагмента:
var selectProductQuery =
@"
SELECT
Id,
Name,
Price
FROM
Products
WHERE
Name LIKE '" + productName + "%'";
command.CommandText = selectProductQuery;
var reader = command.ExecuteReader();
Надо использовать следующий код:
command.CommandText =
@"
SELECT
Id,
Name,
Price
FROM
Products
WHERE
Name LIKE @p_productName";
command.Parameters.AddWithValue("p_productName", productName);
var reader = command.ExecuteReader();
В этом случае, какой бы текст пользователь не ввел в поле поиска продукта, приложение будет искать этот текст в качестве имени продукта, и если в тексте содержится нерелевантный фрагмент (например, с SQL-выражениями), никакой инъекции не произойдет, и продукт просто-напросто не будет найден.
Используйте хранимые процедуры
С технической точки зрения, это правило идентично предыдущему: пользовательский ввод не используется при динамической генерации SQL-запроса. Код хранимой процедуры неизменный и хранится в самой СУБД, а не в коде приложения.
Используйте белый список валидации
В некоторых случаях невозможно использовать параметризацию запросов.
Например, имя таблицы, из которой происходит выборка SELECT
, не может быть параметром, и в этом случае сам текст запроса формируется в зависимости от ввода пользователя.
В таком случае необходимо ограничить список допустимых значений, которые могут прийти от пользователя («белый список»).
Например, если из выпадающего списка веб-формы приходит значение Customers
, то мы производим выборку из таблицы Customers
, а если значение Supplier
» — то из таблицы Suppliers
.
Но если придет значение System_Users
, которого приходить не должно, то это, скорее всего, значит, что мы имеем дело с злоумышленником, который с помощью Swagger или подобной программы пытается проверить наше приложение на прочность:
switch (param)
{
case "Customers":
tableName = "Customers";
break;
case "Suppliers":
tableName = "Suppliers";
break;
default:
throw new InputValidationException("Unexpected value.");
}
Валидируйте пользовательский ввод
Вообще в работе с пользовательским вводом руководствуйтесь принципом «пользователь — всегда потенциальный злоумышленник». Бэкенд не имеет права слепо доверять ничему, что приходит с клиента, даже если клиентское приложение валидирует пользовательский ввод. Злоумышленник может использовать Swagger, автоматические скрипты и другие средства преодоления клиентской валидации.
Поменьше привилегий
Системный пользователь (системная учетная запись), которая осуществляет доступ к данным, должна иметь как можно меньше привилегий на сервере.