Налаштування захисту від 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.ua/search?q=test" --dbs --batch
# Для POST-запитів
sqlmap -u "https://example.ua/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 день







