FlawOpen

flawopen.com/SQL Injection в C#/EF Core FromSqlRaw

Безопасен ли FromSqlRaw в Entity Framework Core?

Справочное руководство
Короткий ответ

Да, если значения передаются отдельно от SQL-текста. FromSqlRaw принимает SQL напрямую: конкатенация или интерполяция недоверенных данных в строку может изменить запрос. Передавайте значения отдельными аргументами параметров либо передавайте интерполированный FormattableString в FromSqlInterpolated (или в FromSql начиная с EF Core 7). Параметр — это запечатанный слот для значения: база получает значение, а не новые инструкции SQL.

НЕБЕЗОПАСНО — интерполяция превращается в обычную строку
// Сначала интерполяция вычисляется в обычную строку.
var sql = $"SELECT * FROM Users WHERE Id = {id}";

// FromSqlRaw получает SQL-текст, а не FormattableString.
var query = context.Users.FromSqlRaw(sql);
БЕЗОПАСНО — значения остаются параметрами
// EF Core 3–6: позиционный placeholder и отдельное значение.
var byRawParameter = context.Users.FromSqlRaw(
    "SELECT * FROM Users WHERE Id = {0}", id);

// EF Core 3+: передайте интерполяцию в API с параметризацией.
var byInterpolation = context.Users.FromSqlInterpolated(
    $"SELECT * FROM Users WHERE Id = {id}");

// EF Core 7+: FromSql также принимает FormattableString.
var byFromSql = context.Users.FromSql(
    $"SELECT * FROM Users WHERE Id = {id}");

Неочевидный нюанс

FromSqlInterpolated принимает FormattableString. EF Core читает формат и аргументы и передаёт каждое интерполированное значение в базу как отдельный параметр; перехвата на этапе компиляции нет. В EF Core 7 появился FromSql с тем же безопасным поведением для интерполированных строк. В предыдущих версиях используйте FromSqlInterpolated. FromSqlRaw также безопасен, если placeholder, например {0}, соответствует значению, переданному отдельным аргументом. Если сначала сохранить интерполированную строку в string, метаданные параметров уже потеряны.

Параметры связывают значения, а не имена столбцов

Параметр базы данных задаёт значение, но не имя таблицы или столбца, оператор SQL или направление сортировки. Если структура запроса должна меняться, выбирайте каждый идентификатор из фиксированного списка разрешённых значений в коде приложения. Не пытайтесь параметризовать идентификаторы и не заменяйте список фильтрацией символов. Также проверьте права доступа и стоимость запроса: допустимый столбец всё ещё может раскрыть чувствительные данные или вызвать дорогое сканирование.

Как проверить кодовую базу

rg -n -g '*.cs' '(FromSqlRaw|FromSqlInterpolated|FromSql)\s*\(' .
Для каждого вызова проверьте перегрузку и тип аргумента. Проследите недоверенные данные до конкатенации, интерполяции для FromSqlRaw или преобразования FormattableString в string; динамические идентификаторы проверяйте отдельно.

Частые вопросы

Безопасен ли FromSqlRaw, если передать ему параметры?

Да, если в SQL есть placeholders, а значения передаются отдельными аргументами, например FromSqlRaw("... {0}", value). Не добавляйте значение в SQL-строку конкатенацией или интерполяцией заранее.

Почему FromSqlInterpolated безопасен с интерполированной строкой C#?

Он принимает FormattableString, который хранит формат и значения отдельно. EF Core преобразует каждое значение в параметр базы данных. Если сначала присвоить результат интерполяции переменной string или передать его в FromSqlRaw, защита теряется.

Можно ли параметрами заменить имена таблиц или столбцов?

Нет. Параметры представляют только значения. Для динамических идентификаторов сопоставляйте выбор пользователя с фиксированным списком допустимых имён и проверяйте права доступа и стоимость запроса.

Источники

См. также SQL-инъекции (CWE-89): как они работают и как их предотвратить в любом языке — flawopen.com FastAPI и SQL Injection: границы защиты Pydantic GORM Raw SQL: защита от SQL-инъекций в Go Достаточно ли экранировать кавычки от SQL Injection? Достаточно ли ORM, чтобы предотвратить SQL-инъекции? Защищает ли Knex.js от SQL-инъекций? MyBatis ${} и #{}: защита от SQL Injection