FlawOpen

flawopen.com/SQL Injection/Числовые ID

Чем опасно форматирование строки даже для числовых ID?

Справочная страница
Краткий ответ

Риск не зависит от реального смысла значения: запрос собирается склейкой текста. «Это всегда число» — предположение о данных, а не гарантия, обеспеченная кодом.

Когда предположение нарушается

HTTP-параметры всегда приходят строками

Параметр URL или поле формы передаётся как текст независимо от ожидаемого типа столбца базы данных. Без явной проверки фреймворк не мешает отправить id=1 OR 1=1 вместо числа до того, как значение попадёт в код.

Будущий рефакторинг может незаметно изменить источник значения

Код, безопасный сегодня благодаря ID из доверенного внутреннего источника, станет уязвимым, если новый вызывающий код передаст в ту же функцию пользовательское значение. Уязвимость всё это время оставалась скрытой.

Преобразование типов может скрыть фактическое значение

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

Параметризуйте запрос даже после проверки типа

Проверьте ID на соответствие ожидаемому формату приложения, затем передайте его через API параметров драйвера базы данных. Проверка защищает контракт входных данных, а параметризация не позволяет интерпретировать ввод как синтаксис SQL. Поведение драйверов на уровне протокола может различаться, поэтому не предполагайте, что все они передают значения одинаково. Параметры PostgreSQL не могут представлять имена таблиц или столбцов; выбирайте динамическую структуру запроса из фиксированного списка приложения.

Связывайте числовое значение и проверяйте его смысл

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

НЕБЕЗОПАСНО — подставлять ID в текст SQL
const rawId = new URL(request.url).searchParams.get("id");
const result = await pool.query(
  `SELECT id FROM users WHERE id = ${rawId}`
);
БЕЗОПАСНО — проверить ID и передать как значение
const rawId = new URL(request.url).searchParams.get("id");
if (rawId === null || !/^[0-9]+$/.test(rawId)) {
  return new Response("Invalid id", { status: 400 });
}
const id = Number(rawId);
if (!Number.isSafeInteger(id) || id < 1) {
  return new Response("Invalid id", { status: 400 });
}
const result = await pool.query(
  "SELECT id FROM users WHERE id = $1",
  Array.of(id)
);

Источники

См. также SQL-инъекции (CWE-89): как они работают и как их предотвратить в любом языке — flawopen.com Что такое параметризованный запрос? Защищает ли Prisma $queryRaw от SQL-инъекций? Безопасна ли интерполяция в Rails where()? Защищает ли Sequelize query() от SQL-инъекций? Защищает ли SQLAlchemy text() от SQL-инъекций? Предотвращает ли проверка sqlx при компиляции SQL-инъекции?