← Все статьи

Три шаблона SQL-инъекций в Node.js, которые всё ещё доезжают до прода

Конкатенация, шаблонные строки и «безопасная» переменная sql — почему ревью пропускает уязвимости и как ловить их eslint-plugin-pg.

Содержание

Коротко

SQL-инъекции в Node.js не исчезли: TypeScript и ревью кода часто пропускают три привычных шаблона в node-postgres. На Dev.to показали, как правило eslint-plugin-pg ловит их статически — прямо в редакторе, без тяжёлого статического анализа безопасности.

Что произошло

Сценарий знакомый: линтер чист, ревьюер одобрил, релиз ушёл в прод. Через три месяца пентестер находит строку вроде pool.query("SELECT * FROM orders WHERE user_id = " + req.query.userId). Параметризованные запросы все знают, OWASP давно помечает инъекции как критичный риск — но уязвимости всё равно проскакивают, потому что каждый шаблон выглядит безобидно по одной строке.

Автор выделяет три формы. Прямая конкатенация в первом аргументе .query(). Шаблонные строки с ${userId} — синтаксис чище, риск тот же. Присваивание в переменную: const sql = "…" + category и отдельно client.query(sql) — на вызове ничего не бросается в глаза, а отравление строки случилось двумя строками выше.

Правило pg/no-unsafe-query заточено под pg, а не под «любую строку рядом с SELECT». Оно срабатывает только на .query(), понимает контракт $1 + массив значений и помечает переменную «загрязнённой» в момент присваивания. Обёртка sanitize() не спасает — правило не может доказать, что она эквивалентна параметризации.

Почему это важно

Универсальные линтеры либо шумят на безобидную сборку строк, либо пропускают шаблонные литералы. ESLint в пределах одной функции не отслеживает «загрязнение» через весь проект, зато работает на каждом сохранении и в хуке перед коммитом — там, где разработчик реально правит SQL.

Команды на Prisma, Knex или Drizzle тоже не застрахованы: $queryRaw, knex.raw и аналоги возвращают те же три шаблона. Для сервисов на чистом pg — внутренние API, пайплайны, микросервисы — поверхность атаки как раз здесь.

На практике

  1. Подключите eslint-plugin-pg и включите pg/no-unsafe-query как ошибку сборки.
  2. Везде, где пользовательский ввод попадает в SQL, используйте $1, $2 и второй аргумент-массив — не конкатенацию и не ${} без плейсхолдеров $N.
  3. Не «рефакторите» параметризованный запрос в «читаемую» строку — типичный путь к регрессии.
  4. Для имён схем/таблиц — pg-format или отдельный слой, а не + SCHEMA_NAME (известный ложноположительный случай правила).
  5. Semgrep/CodeQL оставьте для межпроцедурного анализа; ESLint — для мгновенной обратной связи в редакторе.

Итог

SQL-инъекции в Node.js — не проблема «невежественных джунов», а проблема шаблонов, которые обманывают ревью. Специализированное ESLint-правило для pg закрывает три реальных формы; материал на Dev.to стоит прочитать, если у вас в коде ещё живёт query("…" + id).