Настройка защиты от SQL-инъекций на сайте
SQL-инъекция — атака, при которой злоумышленник встраивает произвольный SQL-код в запросы приложения. Результат: утечка всей базы данных, обход авторизации, изменение или удаление данных. По статистике OWASP, это стабильно входит в тройку самых критичных веб-уязвимостей.
Основная причина уязвимости
Прямая конкатенация пользовательского ввода в SQL:
// УЯЗВИМО
$query = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'";
// Ввод атакующего: ' OR '1'='1
// Результат: SELECT * FROM users WHERE email = '' OR '1'='1'
// Возвращает всех пользователей
Подготовленные запросы (Prepared Statements)
Единственный надёжный способ — параметризованные запросы. Значения никогда не интерполируются в SQL-строку:
PDO (PHP):
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND active = ?');
$stmt->execute([$email, 1]);
$user = $stmt->fetch();
Eloquent ORM (Laravel):
// Все методы ORM используют подготовленные запросы автоматически
$user = User::where('email', $email)->where('active', true)->first();
// Если нужен сырой SQL — bindParam обязателен
$users = DB::select('SELECT * FROM users WHERE email = ?', [$email]);
Sequelize (Node.js):
const user = await User.findOne({ where: { email, active: true } });
// Сырой запрос
const [results] = await sequelize.query(
'SELECT * FROM users WHERE email = :email',
{ replacements: { email }, type: QueryTypes.SELECT }
);
ORM — не гарантия безопасности
Некоторые методы ORM принимают сырые фрагменты и уязвимы:
// Laravel — ОПАСНО
User::whereRaw("name = '$name'")->get();
User::orderBy($request->get('sort'))->get(); // уязвима к ORDER BY injection
// БЕЗОПАСНО
User::whereRaw('name = ?', [$name])->get();
$allowedSorts = ['name', 'email', 'created_at'];
$sort = in_array($request->sort, $allowedSorts) ? $request->sort : 'name';
User::orderBy($sort)->get();
Валидация и whitelist для динамических частей
Параметры запроса, которые нельзя параметризовать (имена таблиц, столбцов, направление сортировки), проверяют по whitelist:
$allowedColumns = ['title', 'created_at', 'views'];
$allowedDirections = ['asc', 'desc'];
$column = in_array($request->column, $allowedColumns)
? $request->column
: throw new InvalidArgumentException('Invalid column');
$direction = in_array($request->direction, $allowedDirections)
? $request->direction
: 'asc';
Принцип минимальных привилегий
Пользователь базы данных, от имени которого работает приложение, должен иметь только необходимые права:
-- Создать пользователя только с нужными правами
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.* TO 'app_user'@'localhost';
-- Без DROP, CREATE, ALTER, GRANT
Даже при успешной инъекции атакующий не сможет дропнуть таблицы или создать новые.
WAF как дополнительный слой
Web Application Firewall (ModSecurity, Cloudflare WAF) перехватывает типичные SQL-инъекции на уровне HTTP до того, как запрос достигает приложения. Не замена подготовленным запросам — дополнительный барьер.
Сканирование уязвимостей
# sqlmap — автоматический поиск SQL-инъекций
sqlmap -u "https://example.com/search?q=test" --dbs --batch
# Для POST-запросов
sqlmap -u "https://example.com/login" --data="email=test&password=test" --dbs
Запускать только на собственных системах или при наличии письменного разрешения.
Хранимые процедуры
Хранимые процедуры не дают автоматической защиты, если внутри них конкатенируют строки:
-- УЯЗВИМО
CREATE PROCEDURE GetUser(IN userInput VARCHAR(255))
BEGIN
SET @sql = CONCAT('SELECT * FROM users WHERE name = ''', userInput, '''');
PREPARE stmt FROM @sql;
EXECUTE stmt;
END;
-- БЕЗОПАСНО — использовать параметризованные запросы внутри процедур
Аудит кода
При работе с legacy-проектом ищем паттерны:
# Поиск потенциально опасных конструкций в PHP
grep -rn 'query.*\$_\(GET\|POST\|REQUEST\)' src/
grep -rn 'whereRaw.*\.' app/
grep -rn '"SELECT.*".*\.' src/
Срок реализации
- Аудит существующего кода: 1–3 дня в зависимости от объёма
- Исправление уязвимых запросов: 2–7 дней
- Настройка WAF + мониторинга: 1 день







