HostiServer
2025-12-17 09:00:00
Захист від SQL Injection у 2026: повний гайд від Hostiserver
⏱️ Час читання: ~10 хвилин | 📅 Оновлено: 17 грудня 2025
SQL Injection у 2026: чому це досі проблема
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 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 | Дані йдуть на зовнішній сервер | Дуже висока |
Свіжі CVE: SQL Injection у 2024-2025
SQLi — не архаїчна проблема. Критичні вразливості знаходять навіть у сучасному enterprise-софті. Ось кілька прикладів, які ми відстежували:
CVE-2025-25257: Fortinet FortiWeb
Іронічний кейс: SQL Injection у самому WAF. CVSS 9.6 Critical. Вразливість дозволяла неавтентифікованому атакуючому виконувати SQL-команди через HTTP-запити. (Джерело інформації).
CVE-2025-1094: PostgreSQL
Критична вразливість у функціях екранування PostgreSQL. CVSS 8.1 High. Обхід prepared statements через некоректну обробку multibyte-символів. Зачепила всі версії до 17.3. (Джерело інформації).
CVE-2024-42327: Zabbix
CVSS 9.9 Critical. Будь-який користувач з API-доступом міг експлуатувати SQLi та ескалювати привілеї. Zabbix використовують тисячі компаній для моніторингу інфраструктури. (Джерело інформації).
MOVEit Transfer (CVE-2023-34362)
Цей кейс досі згадують як приклад масштабу проблеми. SQLi призвела до компрометації понад 2,500 організацій. (Джерело інформації).
Що може зробити атакуючий
- Викрасти дані — паролі, email, платіжні дані
- Змінити дані — підробити транзакції, змінити ціни
- Отримати shell-доступ — через INTO OUTFILE
- Знищити базу — DROP TABLE, TRUNCATE
- Ескалювати привілеї — отримати admin-доступ
Prepared Statements: єдиний правильний підхід
Prepared Statements (параметризовані запити) — найефективніший захист від SQL Injection. Вони відокремлюють SQL-код від даних. Ми рекомендуємо це як базовий стандарт для всіх проєктів.
PHP PDO (рекомендуємо)
// ✅ БЕЗПЕЧНО — 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);
PHP MySQLi
// ✅ БЕЗПЕЧНО — 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:
- SQL-запит компілюється окремо від даних
- Параметри передаються як значення, а не як код
- Спеціальні символи автоматично екрануються
- Навіть
' OR '1'='1стає просто рядком
✅ Наша рекомендація: ЗАВЖДИ використовуйте prepared statements для SQL-запитів з user input. Без винятків.
Важливо: CVE-2025-1094
У лютому 2025 виявили, що навіть prepared statements можуть бути обійдені при некоректній обробці multibyte-символів у PostgreSQL. Рішення просте: тримайте софт оновленим (PostgreSQL 17.3+, 16.7+, 15.11+).
ORM та фреймворки: вбудований захист
Сучасні фреймворки мають захист від SQL Injection "з коробки". На серверах Hostiserver ми підтримуємо всі популярні фреймворки — Laravel, Django, Node.js стек.
Laravel Eloquent (PHP)
// ✅ БЕЗПЕЧНО — 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 (Python)
# ✅ БЕЗПЕЧНО — 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}'")
Node.js (Sequelize / Prisma)
// ✅ БЕЗПЕЧНО — 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 все ще може бути вразливим. Ми часто бачимо це при аудиті клієнтських проєктів.
Input Validation: додатковий рівень
Валідація — це додатковий захист, а не заміна prepared statements. Ми рекомендуємо застосовувати обидва підходи одночасно.
Типи валідації
| Тип | Опис | Приклад |
|---|---|---|
| Whitelist | Дозволяємо тільки очікувані значення | Сортування: тільки 'asc' або 'desc' |
| Type casting | Примусове перетворення типу | $id = (int) $_GET['id']; |
| Format validation | Перевірка формату даних | Email, дата, UUID |
| Length limits | Обмеження довжини | Username: max 50 символів |
Приклади валідації в PHP
// ✅ 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: як ми налаштовуємо на серверах Hostiserver
WAF аналізує HTTP-запити і блокує підозрілі патерни до того, як вони досягнуть застосунку. Особливо важливо для legacy-коду, який складно переписати.
Що ми використовуємо
| Рішення | Рівень | Застосування |
|---|---|---|
| ModSecurity | Серверний (Apache/Nginx) | Глибока інспекція запитів |
| Cloudflare WAF | DNS-проксі | Edge protection, DDoS |
ModSecurity правила (наша конфігурація)
Ось приклад правил, які ми налаштовуємо для клієнтів:
# Правило 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 Core Rule Set (CRS)
Для повноцінного захисту ми встановлюємо 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 — те, що пройшло.
MySQL Hardening: наші стандартні налаштування
Навіть якщо атакуючий знайде 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 — не змінить конфігурацію сервера.
Моніторинг: як ми виявляємо атаки
Превентивний захист важливий, але потрібно також бачити, що відбувається в реальному часі.
MySQL логування
# /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
Audit logging
Для compliance та forensics ми використовуємо:
- MariaDB Audit Plugin — безкоштовний для MariaDB
- MySQL Enterprise Audit — для MySQL Enterprise
# MariaDB Audit Plugin
INSTALL SONAME 'server_audit';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';
На що налаштовуємо alerts
- Аномально велика кількість запитів з одного IP
- Запити з підозрілими патернами (UNION, SLEEP)
- Помилки автентифікації
- Зміни структури бази даних
SIEM для Enterprise
Для enterprise клієнтів пропонуємо інтеграцію з Elastic Security (ELK SIEM) — централізований збір логів, кореляція подій, автоматичне виявлення загроз.
💡 Порада: Slow query log — не тільки про performance. Аномально повільні запити можуть бути ознакою time-based SQLi (SLEEP, BENCHMARK).
Рекомендовані версії на 2026 рік
Застарілі версії PHP та MySQL мають відомі вразливості. Ось що ми рекомендуємо клієнтам:
PHP
| Версія | Статус | Наша рекомендація |
|---|---|---|
| PHP 8.4 | ✅ Active Support | Найкращий вибір |
| PHP 8.3 | ✅ Active Support | Рекомендовано |
| PHP 8.2 | ⚠️ Security Only | Мінімальна версія |
| PHP 8.1 та нижче | ❌ End of Life | Терміново оновлюйте! |
MySQL / MariaDB
| Версія | Статус | Наша рекомендація |
|---|---|---|
| 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 (після CVE-2025-1094)
Якщо використовуєте PostgreSQL — оновіть до версій 17.3+, 16.7+, 15.11+, 14.16+, або 13.19+.
🔴 Критично: MySQL 5.7 та PHP 7.x більше не отримують security updates. Якщо ви на цих версіях — зверніться до нас, допоможемо з міграцією.
Checklist: перевірте свій проєкт
🔐 Код застосунку
- ☐ Всі SQL-запити використовують prepared statements
- ☐ PDO:
ATTR_EMULATE_PREPARES = false - ☐ ORM/Query Builder замість raw SQL
- ☐ Whitelist валідація для ORDER BY, назв таблиць
- ☐ Type casting для числових параметрів
- ☐ Error messages не показують SQL-запити
🛡️ Серверна інфраструктура
- ☐ WAF (ModSecurity / Cloudflare) активний
- ☐ MySQL
bind-address = 127.0.0.1 - ☐ Порт 3306 закритий ззовні
- ☐ Окремий database user для кожного застосунку
- ☐ Мінімальні привілеї (SELECT, INSERT, UPDATE, DELETE)
- ☐ Заборонено SUPER, FILE, PROCESS
📊 Моніторинг
- ☐ Error log увімкнено
- ☐ Slow query log увімкнено
- ☐ Alerts на підозрілу активність
🔄 Maintenance
- ☐ PHP 8.2+ (рекомендовано 8.3/8.4)
- ☐ MySQL 8.0+ / MariaDB 10.11+
- ☐ PostgreSQL 17.3+ (якщо використовуєте)
- ☐ Регулярні бекапи
🛡️ Потрібна допомога з безпекою?
Ми можемо провести аудит вашого проєкту, налаштувати WAF, hardening бази даних та моніторинг.
Що входить в managed сервери Hostiserver:
- ModSecurity WAF з правилами для SQLi
- Cloudflare інтеграція
- MySQL hardening за нашими стандартами
- Окремі database користувачі
- Моніторинг та alerts
- Регулярні бекапи
Для Enterprise:
- Elastic Security (ELK SIEM)
- MySQL Audit Plugin
- TLS encryption
- 24/7 DevOps підтримка
FAQ: Часті питання
- Чи захищає WordPress від SQL Injection?
-
WordPress core використовує
$wpdb->prepare()і добре захищений. Але плагіни та теми — інша історія. За нашим досвідом, більшість зламів WordPress відбувається саме через вразливі плагіни.Рекомендуємо: Використовуйте перевірені плагіни, регулярно оновлюйте, встановіть WAF.
- Prepared statements захищають на 100%?
-
Prepared statements захищають від ін'єкції значень. Але є елементи, які не параметризуються: назви таблиць, ORDER BY, LIMIT. Для них — whitelist валідація.
Також враховуйте CVE-2025-1094: PostgreSQL мав вразливість в самих функціях екранування. Тримайте софт оновленим.
- Чи достатньо тільки WAF?
-
Ні. WAF — важливий рівень, але не панацея. CVE-2025-25257 показав, що навіть Fortinet FortiWeb (сам WAF!) мав критичну SQLi вразливість.
Правильний підхід: prepared statements + validation + WAF + database hardening.
- PDO чи MySQLi?
-
Обидва безпечні при правильному використанні:
- PDO — підтримує 12+ СУБД, іменовані placeholders, рекомендуємо для нових проєктів
- MySQLi — тільки MySQL/MariaDB, трохи швидший
Важливо для PDO:
ATTR_EMULATE_PREPARES = false. - Як перевірити сайт на SQL Injection?
-
Інструменти:
- SQLMap — автоматизований тестер
- Burp Suite — intercepting proxy
- OWASP ZAP — безкоштовний сканер
Важливо: Тестуйте тільки власні сайти. Тестування чужих без дозволу — злочин.
- Що робити, якщо вже зламали?
-
- Ізолюйте сервер від мережі
- Зробіть backup для forensics
- Перевірте логи на точку входу
- Відновіть з чистого backup
- Виправте вразливість
- Змініть всі паролі
- Повідомте користувачів про витік
Якщо потрібна допомога з incident response — звертайтесь до нашої підтримки.
- Чи потрібен окремий database user для кожного сайту?
-
Так. Ізоляція — критичний елемент. Зламають один сайт — інші бази залишаться захищеними. Ми налаштовуємо це за замовчуванням на всіх managed серверах.