← Назад к вопросам

Как защититься от SQL Injection?

2.0 Middle🔥 221 комментариев
#Базы данных и SQL#Безопасность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как защититься от SQL Injection?

SQL Injection — одна из самых опасных уязвимостей в веб приложениях. Это атака, когда злоумышленник внедряет SQL код в пользовательский ввод. Я расскажу о всех способах защиты.

Что такое SQL Injection (примеры атак)

// УЯЗВИМЫЙ КОД - НИКОГДА ТАК НЕ ДЕЛАЙ!
const userId = req.query.id; // пользователь передаёт: 1 OR 1=1
const query = `SELECT * FROM users WHERE id = ${userId}`;
// Результат: SELECT * FROM users WHERE id = 1 OR 1=1
// Вернёт ВСЕ пользователей вместо одного!
// Ещё опаснее - удаление данных
// Пользователь передаёт: 1; DROP TABLE users; --
const query = `SELECT * FROM users WHERE id = ${userId}`;
// Результат: SELECT * FROM users WHERE id = 1; DROP TABLE users; --
// Удалит всю таблицу!

Способ 1: Параметризованные запросы (ГЛАВНОЕ ОРУЖИЕ)

Это основной и самый надёжный способ защиты от SQL Injection.

// Правильно - с параметризацией
const userId = req.query.id;

// node-postgres (pg)
const result = await db.query(
  'SELECT * FROM users WHERE id = $1',
  [userId] // параметр передаётся отдельно
);

// MySQL (mysql2)
const [rows] = await connection.execute(
  'SELECT * FROM users WHERE id = ?',
  [userId]
);

// Sqlite3
db.get(
  'SELECT * FROM users WHERE id = ?',
  [userId],
  (err, row) => { /* ... */ }
);

Почему это работает: База данных получает SQL структуру и данные отдельно. Даже если пользователь передаст 1 OR 1=1, база обработает это как строку, а не SQL код.

// TypeORM (ORM автоматически параметризует)
const user = await userRepository.findOne({
  where: { id: userId }
});

// Prisma
const user = await prisma.user.findUnique({
  where: { id: userId }
});

// Sequelize
const user = await User.findOne({
  where: { id: userId }
});

Способ 2: Используй ORM (Object-Relational Mapping)

ORMs автоматически параметризуют запросы.

// TypeORM
const user = await db.getRepository(User)
  .createQueryBuilder('user')
  .where('user.email = :email', { email: userEmail })
  .getOne();

// Или simplest way
const users = await db.getRepository(User).find({
  where: { email: userEmail }
});

// Sequelize
const user = await User.findOne({
  where: { email: userEmail }
});

// Prisma
const user = await prisma.user.findUnique({
  where: { email: userEmail }
});

Способ 3: Валидация входа

Добавь слой валидации перед БД.

import { validate, IsUUID, IsEmail, IsInt } from 'class-validator';

class GetUserDto {
  @IsUUID()
  id: string; // Только UUID формат
}

class LoginDto {
  @IsEmail()
  email: string; // Только валидный email
}

// В контроллере
async function getUser(req: Request) {
  const dto = new GetUserDto();
  dto.id = req.params.id;
  
  const errors = await validate(dto);
  if (errors.length > 0) {
    throw new BadRequestException('Invalid ID');
  }
  
  // Только теперь обращаемся к БД
  return await userService.findById(dto.id);
}

Способ 4: Используй prepared statements

// Node.js с pg (PostgreSQL)
const { Pool } = require('pg');
const pool = new Pool();

// Плохо
const query = 'SELECT * FROM users WHERE id = ' + userId;
const result = await pool.query(query);

// Хорошо
const result = await pool.query(
  'SELECT * FROM users WHERE id = $1',
  [userId]
);

Способ 5: Санитизация (вспомогательное средство)

Удаление опасных символов. Не заменяет параметризацию!

function escapeSql(str) {
  return str.replace(/'/g, "''");
}

// Плохо - всё равно уязвимо
const name = escapeSql(req.body.name);
const query = `SELECT * FROM users WHERE name = '${name}'`; // УЯЗИМО!

// Хорошо - комбинируй с параметризацией
const result = await db.query(
  'SELECT * FROM users WHERE name = $1',
  [req.body.name]
);

Способ 6: Принцип наименьших привилегий (DB User)

Дай приложению БД пользователя с минимальными правами.

-- Создай отдельного пользователя для приложения
CREATE USER app_user WITH PASSWORD 'strong_password';

-- Дай только необходимые права
GRANT SELECT, INSERT, UPDATE ON public.users TO app_user;

-- НЕ дай права на удаление или DROP
REVOKE DELETE ON public.users FROM app_user;

-- Подключайся в приложении как app_user
// DATABASE_URL=postgres://app_user:pwd@localhost/db

Если взломают приложение, они не смогут удалить БД.

Способ 7: Rate Limiting и мониторинг

import rateLimit from 'express-rate-limit';

// Ограничь количество запросов
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 минут
  max: 100 // максимум 100 запросов
});

app.use('/api/', limiter);

// Мониторь странные паттерны
app.use((req, res, next) => {
  if (req.query.id?.includes('OR') || req.query.id?.includes(';')) {
    console.warn('Suspicious SQL pattern detected', req.query);
    // Залогируй, заблокируй IP
  }
  next();
});

Полный пример безопасного кода

import express, { Request, Response } from 'express';
import { Pool } from 'pg';
import { validate, IsUUID } from 'class-validator';

const pool = new Pool();

class GetUserDto {
  @IsUUID()
  id: string;
}

router.get('/users/:id', async (req: Request, res: Response) => {
  try {
    // Валидация входа
    const dto = new GetUserDto();
    dto.id = req.params.id;
    
    const errors = await validate(dto);
    if (errors.length > 0) {
      return res.status(400).json({ error: 'Invalid ID' });
    }

    // Параметризованный запрос
    const result = await pool.query(
      'SELECT id, email, name FROM users WHERE id = $1',
      [dto.id]
    );

    if (result.rows.length === 0) {
      return res.status(404).json({ error: 'User not found' });
    }

    res.json(result.rows[0]);
  } catch (err) {
    console.error('Database error:', err);
    res.status(500).json({ error: 'Internal server error' });
  }
});

Чеклист защиты

  • Используешь параметризованные запросы ВСЕГда
  • Никогда не конкатенируешь пользовательский ввод в SQL
  • Используешь ORM или prepared statements
  • Валидируешь входные данные
  • Даёшь БД пользователю только нужные права
  • Логируешь подозрительные запросы
  • Используешь HTTPS для всех запросов
  • Регулярно проверяешь логи на атаки

Вывод: SQL Injection легко предотвратить, если ВСЕГДА использовать параметризованные запросы. Это правило номер 1 для безопасности БД.

Как защититься от SQL Injection? | PrepBro