⏱️ Час читання: ~10 хвилин | 📅 Оновлено: 17 грудня 2025
SQL Injection залишається в OWASP Top 10 вже понад 20 років. У листопаді 2025 OWASP опублікував оновлений рейтинг, де Injection посідає 5-те місце (A05:2025). FBI та CISA офіційно визнали SQLi "unforgivable defect" — вразливістю, яка не повинна існувати в сучасному софті.
Але вона існує. І ми в Hostiserver бачимо це регулярно: клієнти приходять після зламу, з пошкодженими базами, витоком даних користувачів. У більшості випадків причина — відсутність prepared statements або застарілий код без валідації.
Цей гайд — не теоретичний огляд. Ми зібрали конфігурації та підходи, які реально використовуємо на managed серверах Hostiserver: від налаштування ModSecurity до MySQL hardening. Все перевірено на практиці.
⚠️ Важливо: Якщо ваш сайт приймає будь-який user input (форми, пошук, фільтри, URL-параметри) — він потенційно вразливий. Навіть "простий блог" на WordPress може стати жертвою через вразливий плагін.
SQL Injection — техніка атаки, при якій зловмисник вставляє шкідливий SQL-код у поля введення. Якщо застосунок не валідує input, цей код виконується на сервері бази даних.
// ❌ НЕБЕЗПЕЧНО — ніколи так не робіть!
$username = $_POST['username'];
$query = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $query);
Якщо зловмисник введе в поле username:
' OR '1'='1' --
Запит перетвориться на:
SELECT * FROM users WHERE username = '' OR '1'='1' --'
Результат: зловмисник отримує доступ до всіх записів таблиці.
| Тип | Механізм | Складність виявлення |
|---|---|---|
| Classic (In-band) | Результат видно на сторінці | Низька |
| Union-based | UNION витягує дані з інших таблиць | Низька |
| Error-based | Дані через повідомлення про помилки | Середня |
| Blind SQLi | Немає видимого результату, "вгадування" | Висока |
| Time-based Blind | SLEEP() визначає істинність умов | Висока |
| Out-of-band | Дані йдуть на зовнішній сервер | Дуже висока |
SQLi — не архаїчна проблема. Критичні вразливості знаходять навіть у сучасному enterprise-софті. Ось кілька прикладів, які ми відстежували:
Іронічний кейс: SQL Injection у самому WAF. CVSS 9.6 Critical. Вразливість дозволяла неавтентифікованому атакуючому виконувати SQL-команди через HTTP-запити. (Джерело інформації).
Критична вразливість у функціях екранування PostgreSQL. CVSS 8.1 High. Обхід prepared statements через некоректну обробку multibyte-символів. Зачепила всі версії до 17.3. (Джерело інформації).
CVSS 9.9 Critical. Будь-який користувач з API-доступом міг експлуатувати SQLi та ескалювати привілеї. Zabbix використовують тисячі компаній для моніторингу інфраструктури. (Джерело інформації).
Цей кейс досі згадують як приклад масштабу проблеми. SQLi призвела до компрометації понад 2,500 організацій. (Джерело інформації).
Prepared Statements (параметризовані запити) — найефективніший захист від SQL Injection. Вони відокремлюють SQL-код від даних. Ми рекомендуємо це як базовий стандарт для всіх проєктів.
// ✅ БЕЗПЕЧНО — PDO з prepared statements
$pdo = new PDO('mysql:host=localhost;dbname=app_db;charset=utf8mb4', $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // Важливо!
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username AND status = :status');
$stmt->execute([
':username' => $username,
':status' => 'active'
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// ✅ БЕЗПЕЧНО — MySQLi з prepared statements
$mysqli = new mysqli('localhost', $user, $pass, 'app_db');
$mysqli->set_charset('utf8mb4');
$stmt = $mysqli->prepare('SELECT * FROM users WHERE username = ? AND status = ?');
$stmt->bind_param('ss', $username, $status);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
При використанні prepared statements:
' OR '1'='1 стає просто рядком✅ Наша рекомендація: ЗАВЖДИ використовуйте prepared statements для SQL-запитів з user input. Без винятків.
У лютому 2025 виявили, що навіть prepared statements можуть бути обійдені при некоректній обробці multibyte-символів у PostgreSQL. Рішення просте: тримайте софт оновленим (PostgreSQL 17.3+, 16.7+, 15.11+).
Сучасні фреймворки мають захист від SQL Injection "з коробки". На серверах Hostiserver ми підтримуємо всі популярні фреймворки — Laravel, Django, Node.js стек.
// ✅ БЕЗПЕЧНО — Eloquent автоматично параметризує
$users = User::where('username', $username)
->where('status', 'active')
->get();
// ✅ БЕЗПЕЧНО — Query Builder
$users = DB::table('users')
->where('username', $username)
->get();
// ❌ НЕБЕЗПЕЧНО — raw запити без bindings
// DB::select("SELECT * FROM users WHERE username = '$username'");
# ✅ БЕЗПЕЧНО — Django ORM
users = User.objects.filter(username=username, status='active')
# ✅ БЕЗПЕЧНО — raw query з параметрами
users = User.objects.raw('SELECT * FROM users WHERE username = %s', [username])
# ❌ НЕБЕЗПЕЧНО — string formatting
# User.objects.raw(f"SELECT * FROM users WHERE username = '{username}'")
// ✅ БЕЗПЕЧНО — Sequelize
const users = await User.findAll({
where: { username: username, status: 'active' }
});
// ✅ БЕЗПЕЧНО — Prisma
const users = await prisma.user.findMany({
where: { username: username, status: 'active' }
});
⚠️ Увага: ORM захищає тільки при правильному використанні. Raw SQL всередині ORM все ще може бути вразливим. Ми часто бачимо це при аудиті клієнтських проєктів.
Валідація — це додатковий захист, а не заміна prepared statements. Ми рекомендуємо застосовувати обидва підходи одночасно.
| Тип | Опис | Приклад |
|---|---|---|
| Whitelist | Дозволяємо тільки очікувані значення | Сортування: тільки 'asc' або 'desc' |
| Type casting | Примусове перетворення типу | $id = (int) $_GET['id']; |
| Format validation | Перевірка формату даних | Email, дата, UUID |
| Length limits | Обмеження довжини | Username: max 50 символів |
// ✅ Whitelist для сортування (ORDER BY не параметризується)
$allowed_columns = ['created_at', 'username', 'email'];
$sort_column = in_array($_GET['sort'], $allowed_columns) ? $_GET['sort'] : 'created_at';
$allowed_directions = ['ASC', 'DESC'];
$sort_dir = in_array(strtoupper($_GET['dir']), $allowed_directions) ? strtoupper($_GET['dir']) : 'DESC';
// ✅ Type casting для ID
$user_id = filter_var($_GET['id'], FILTER_VALIDATE_INT);
if ($user_id === false) {
throw new InvalidArgumentException('Invalid user ID');
}
// ✅ Regex для специфічних форматів
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
throw new InvalidArgumentException('Invalid username format');
}
💡 З нашого досвіду: Whitelist завжди краще за Blacklist. Замість блокування небезпечних символів — дозволяйте тільки очікувані.
WAF аналізує HTTP-запити і блокує підозрілі патерни до того, як вони досягнуть застосунку. Особливо важливо для legacy-коду, який складно переписати.
| Рішення | Рівень | Застосування |
|---|---|---|
| ModSecurity | Серверний (Apache/Nginx) | Глибока інспекція запитів |
| Cloudflare WAF | DNS-проксі | Edge protection, DDoS |
Ось приклад правил, які ми налаштовуємо для клієнтів:
# Правило 1: Виявлення SQLi патернів
SecRule ARGS|REQUEST_BODY \
"@rx (?i)(union\s+select|sleep\(|benchmark\(|or\s+1=1)" \
"id:1001002,phase:2,pass,log,tag:'attack-sqli',setvar:'tx.inbound_anomaly_score=+5',msg:'SQLi pattern detected'"
# Правило 2: Блокування при перевищенні anomaly score
SecRule TX:INBOUND_ANOMALY_SCORE "@ge 5" \
"id:1001099,phase:2,deny,status:403,log,msg:'Inbound anomaly score exceeded'"
UNION SELECT — витягування даних з інших таблицьSLEEP() — time-based blind SQLiBENCHMARK() — альтернативна time-based атакаOR 1=1 — класична boolean injectionДля повноцінного захисту ми встановлюємо OWASP CRS — набір правил, що покриває SQLi, XSS, LFI та інші атаки:
# Встановлення OWASP CRS для Apache
sudo apt install libapache2-mod-security2
sudo mv /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
# Завантажити CRS
cd /etc/modsecurity
sudo git clone https://github.com/coreruleset/coreruleset.git
sudo cp coreruleset/crs-setup.conf.example coreruleset/crs-setup.conf
✅ Наш підхід: Комбінуємо ModSecurity на сервері з Cloudflare WAF. Cloudflare блокує основну масу атак на edge, ModSecurity — те, що пройшло.
Навіть якщо атакуючий знайде SQLi, правильно налаштована база мінімізує шкоду. Ось що ми робимо на managed серверах Hostiserver.
# /etc/mysql/mysql.conf.d/mysqld.cnf
# MySQL слухає тільки localhost — КРИТИЧНО!
bind-address = 127.0.0.1
# Порт 3306 НЕ відкритий у публічний інтернет
# Доступ дозволений тільки з localhost або конкретних IP через firewall
Кожен застосунок отримує окремого користувача з мінімальними правами:
-- ✅ Окремий користувач для застосунку
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'STRONG_RANDOM_PASSWORD';
-- Тільки необхідні права на конкретну базу
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'localhost';
-- ❌ НЕ даємо: GRANT ALL ON *.*
-- ❌ НЕ даємо: SUPER, FILE, PROCESS, SHUTDOWN
FLUSH PRIVILEGES;
| Що робимо | Як |
|---|---|
| Видаляємо анонімних користувачів | DELETE FROM mysql.user WHERE User=''; |
| Root тільки локально | Заборона root@'%' |
| Сильні паролі | Password policy, мін. 16 символів |
| Ізоляція баз | Користувач бачить тільки свою БД |
| TLS для remote | REQUIRE SSL |
# /etc/mysql/mysql.conf.d/mysqld.cnf
max_connections = 150
max_user_connections = 50
# Таймаути — закриття idle з'єднань
wait_timeout = 300
interactive_timeout = 300
⚠️ Чому це важливо: Якщо атакуючий знайде SQLi, він буде обмежений правами app_user. Без FILE — не зможе писати файли. Без SUPER — не змінить конфігурацію сервера.
Превентивний захист важливий, але потрібно також бачити, що відбувається в реальному часі.
# /etc/mysql/mysql.conf.d/mysqld.cnf
# Error log — завжди увімкнено
log_error = /var/log/mysql/error.log
# Slow query log — виявлення підозрілих запитів
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
Для compliance та forensics ми використовуємо:
# MariaDB Audit Plugin
INSTALL SONAME 'server_audit';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';
Для enterprise клієнтів пропонуємо інтеграцію з Elastic Security (ELK SIEM) — централізований збір логів, кореляція подій, автоматичне виявлення загроз.
💡 Порада: Slow query log — не тільки про performance. Аномально повільні запити можуть бути ознакою time-based SQLi (SLEEP, BENCHMARK).
Застарілі версії PHP та MySQL мають відомі вразливості. Ось що ми рекомендуємо клієнтам:
| Версія | Статус | Наша рекомендація |
|---|---|---|
| PHP 8.4 | ✅ Active Support | Найкращий вибір |
| PHP 8.3 | ✅ Active Support | Рекомендовано |
| PHP 8.2 | ⚠️ Security Only | Мінімальна версія |
| PHP 8.1 та нижче | ❌ End of Life | Терміново оновлюйте! |
| Версія | Статус | Наша рекомендація |
|---|---|---|
| MySQL 8.4 LTS | ✅ Long Term Support | Найкращий вибір |
| MySQL 8.0.3x+ | ✅ Active Support | Рекомендовано |
| MariaDB 10.11 LTS | ✅ Long Term Support | Рекомендовано |
| MySQL 5.7 | ❌ End of Life | Критичний ризик! |
Якщо використовуєте PostgreSQL — оновіть до версій 17.3+, 16.7+, 15.11+, 14.16+, або 13.19+.
🔴 Критично: MySQL 5.7 та PHP 7.x більше не отримують security updates. Якщо ви на цих версіях — зверніться до нас, допоможемо з міграцією.
ATTR_EMULATE_PREPARES = falsebind-address = 127.0.0.1Ми можемо провести аудит вашого проєкту, налаштувати WAF, hardening бази даних та моніторинг.
WordPress core використовує $wpdb->prepare() і добре захищений. Але плагіни та теми — інша історія. За нашим досвідом, більшість зламів WordPress відбувається саме через вразливі плагіни.
Рекомендуємо: Використовуйте перевірені плагіни, регулярно оновлюйте, встановіть WAF.
Prepared statements захищають від ін'єкції значень. Але є елементи, які не параметризуються: назви таблиць, ORDER BY, LIMIT. Для них — whitelist валідація.
Також враховуйте CVE-2025-1094: PostgreSQL мав вразливість в самих функціях екранування. Тримайте софт оновленим.
Ні. WAF — важливий рівень, але не панацея. CVE-2025-25257 показав, що навіть Fortinet FortiWeb (сам WAF!) мав критичну SQLi вразливість.
Правильний підхід: prepared statements + validation + WAF + database hardening.
Обидва безпечні при правильному використанні:
Важливо для PDO: ATTR_EMULATE_PREPARES = false.
Інструменти:
Важливо: Тестуйте тільки власні сайти. Тестування чужих без дозволу — злочин.
Якщо потрібна допомога з incident response — звертайтесь до нашої підтримки.
Так. Ізоляція — критичний елемент. Зламають один сайт — інші бази залишаться захищеними. Ми налаштовуємо це за замовчуванням на всіх managed серверах.