Prepared Statements — защита от SQL-инъекций

Prepared Statements (параметризованные запросы) — техника, при которой SQL-запрос компилируется отдельно от данных, что полностью исключает SQL-инъекции: данные никогда не интерпретируются как код.

Зачем нужно

SQL-инъекция — одна из старейших и наиболее критичных уязвимостей: позволяет читать, изменять и удалять данные, обходить авторизацию. Prepared Statements — самый надёжный способ защиты: даже специальные символы (', ", --, ;) передаются как безопасные данные.

Где используется

  • Любые SQL-запросы с пользовательским вводом (поиск, логин, фильтры)
  • ORM-библиотеки (Sequelize, Prisma, Knex) — используют под капотом
  • Хранимые процедуры с параметрами

Основной контент

Уязвимый код (конкатенация строк)

// УЯЗВИМО — никогда не делайте так!
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`;
// Атакующий вводит: ' OR '1'='1
// Результат: SELECT * FROM users WHERE email = '' OR '1'='1'
// → Возвращает ВСЕХ пользователей

node-postgres (pg)

const { Pool } = require('pg');
const pool = new Pool();

// БЕЗОПАСНО — параметр $1 никогда не интерпретируется как SQL
const result = await pool.query(
  'SELECT * FROM users WHERE email = $1',
  [req.body.email]
);

// Несколько параметров
const user = await pool.query(
  'SELECT id, name FROM users WHERE email = $1 AND active = $2',
  [email, true]
);

mysql2 (Node.js)

const mysql = require('mysql2/promise');
const conn = await mysql.createConnection(config);

// Placeholder ? заменяется безопасно
const [rows] = await conn.execute(
  'SELECT * FROM products WHERE category = ? AND price < ?',
  [req.query.category, req.query.maxPrice]
);

Prisma (ORM)

// Prisma использует параметризацию автоматически
const user = await prisma.user.findUnique({
  where: { email: req.body.email },
});

// Raw-запросы — используем Prisma.$queryRaw с шаблонными строками
const result = await prisma.$queryRaw`
  SELECT * FROM users WHERE id = ${userId}
`;
// Prisma автоматически параметризует значение в template literal

Knex.js

// Knex builder автоматически параметризует значения
const users = await knex('users')
  .where({ email: req.body.email, active: true })
  .select('id', 'name');

Частые ошибки

  • Конкатенация строк с данными пользователя в "простых" запросах — нет безопасных случаев
  • Использование $queryRawUnsafe в Prisma или knex.raw с интерполяцией строк
  • Параметризация запроса, но динамическое формирование имён таблиц/колонок без whitelist
  • Доверие данным из заголовков запроса (User-Agent, X-Forwarded-For) без параметризации

Связанные темы

Ресурсы