Параметризуйте

Самое главное правило — данные, присылаемые пользователем, не должны участвовать в формировании текста 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, автоматические скрипты и другие средства преодоления клиентской валидации.

Поменьше привилегий

Системный пользователь (системная учетная запись), которая осуществляет доступ к данным, должна иметь как можно меньше привилегий на сервере.