База данных — это сердце любого сайта, и если её скомпрометируют, восстановление может занять дни, а потери — исчисляться сотнями тысяч рублей. Я за 10+ лет видел достаточно взломов, чтобы написать об этом развёрнуто и без воды.
Почему база данных — главная цель атакующих
Честно говоря, большинство владельцев сайтов думают о безопасности в последнюю очередь. Настроили WordPress или Битрикс, сделали SSL-сертификат — и успокоились. Но злоумышленников интересует не внешний вид вашего сайта, а данные внутри: логины, пароли, email-адреса, платёжная информация, персональные данные клиентов.
У меня был клиент — небольшой интернет-магазин на Laravel + MySQL 8.0. Они не закрыли порт 3306 на файрволе, и однажды утром обнаружили, что база данных зашифрована, а в ней осталась одна таблица с требованием выкупа в биткоинах. Это классическая атака через открытый порт MySQL. Восстановились с бэкапа, но потеряли трое суток и нервы всей команды. После этого случая я всегда начинаю любой аудит с проверки сетевого доступа к БД.
Векторов атак на базу данных несколько. SQL-инъекции — когда через форму на сайте передают вредоносный SQL-код. Брутфорс учётных данных — если пароль root слабый или предсказуемый. Открытые порты — когда MySQL или PostgreSQL торчат в интернет без ограничений. Чрезмерные привилегии — когда один пользователь БД имеет права на всё и сразу. И утечки через phpMyAdmin — когда веб-интерфейс доступен всем желающим.
Разберём каждый аспект настройки доступа детально — от сетевого уровня до уровня приложения.
Сетевая изоляция: первый и самый важный шаг
Правило номер один: MySQL не должен быть доступен из интернета. Никогда. Это плохая идея в любом сценарии, если только у вас нет специфической архитектуры с несколькими серверами в одной приватной сети.
По умолчанию MySQL 8.0 слушает все интерфейсы (0.0.0.0:3306). Нужно это исправить. Открываем /etc/mysql/mysql.conf.d/mysqld.cnf и меняем bind-address:
[mysqld]
# Слушаем только локальный интерфейс
bind-address = 127.0.0.1
# Если нужен доступ только с конкретного IP в локальной сети
# bind-address = 192.168.1.100
# Отключаем сетевой доступ полностью (если БД на том же сервере)
# skip-networking = 1
# Дополнительно — отключаем символические ссылки
symbolic-links = 0
# Логируем все запросы (для аудита, на продакшене осторожно с объёмом)
# general_log = 1
# general_log_file = /var/log/mysql/general.log
После изменения конфига перезапускаем MySQL: systemctl restart mysql. И проверяем файрвол. На Ubuntu/Debian с UFW это выглядит так:
# Блокируем порт 3306 для внешних соединений
ufw deny 3306
# Если у вас несколько серверов в одной сети — разрешаем только им
# ufw allow from 192.168.1.0/24 to any port 3306
# Проверяем, что порт не торчит наружу
ss -tlnp | grep 3306
# Должно показать: 127.0.0.1:3306, а не 0.0.0.0:3306
Если сайт и БД на разных серверах — используйте SSH-туннель или VPN (WireGuard отлично подходит). Никакого прямого доступа к 3306 через интернет. Это не паранойя, это базовая гигиена.
nmap -p 3306 ваш-ip.Принцип минимальных привилегий: настройка пользователей БД
Это второй по важности момент, который игнорируют в 80% проектов, которые я видел. Типичная картина: в конфиге Laravel или WordPress стоит пользователь root с паролем "12345" или вообще без пароля. Это катастрофа.
Для каждого сайта создавайте отдельного пользователя БД с минимально необходимыми правами. Обычному веб-приложению нужны только SELECT, INSERT, UPDATE, DELETE — и то не на все таблицы. Права CREATE, DROP, ALTER нужны только во время деплоя миграций, и то временно.
-- Создаём пользователя только для локального доступа
CREATE USER 'mysite_user'@'localhost' IDENTIFIED BY 'Str0ng!P@ssw0rd#2026';
-- Даём только необходимые права на конкретную БД
GRANT SELECT, INSERT, UPDATE, DELETE ON mysite_db.* TO 'mysite_user'@'localhost';
-- Применяем изменения
FLUSH PRIVILEGES;
-- Проверяем права пользователя
SHOW GRANTS FOR 'mysite_user'@'localhost';
-- Для Laravel — отдельный пользователь для миграций (временный)
CREATE USER 'mysite_migrate'@'localhost' IDENTIFIED BY 'AnotherStr0ng!Pass';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX
ON mysite_db.* TO 'mysite_migrate'@'localhost';
FLUSH PRIVILEGES;
-- После деплоя миграций — удаляем или отзываем права
-- DROP USER 'mysite_migrate'@'localhost';
-- Убираем права root на удалённый доступ
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
FLUSH PRIVILEGES;
Отдельно про пароли. В MySQL 8.0 появилась политика паролей — используйте её. Минимальная длина 12 символов, смешанный регистр, цифры, спецсимволы. Я использую генератор паролей и храню их в Vault или хотя бы в зашифрованном .env файле с правами 600.
Ещё один момент, который часто упускают: анонимные пользователи. В старых версиях MySQL они создавались по умолчанию. Проверяем и удаляем:
-- Проверяем анонимных пользователей
SELECT User, Host FROM mysql.user WHERE User = '';
-- Удаляем
DELETE FROM mysql.user WHERE User = '';
-- Удаляем тестовую базу данных
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db = 'test' OR Db = 'test\\_%';
FLUSH PRIVILEGES;
По сути, это те же действия, что делает mysql_secure_installation, но вручную — так понятнее, что именно происходит.
Защита phpMyAdmin и веб-интерфейса к базе данных
phpMyAdmin — отдельная история. На моей практике это один из самых атакуемых компонентов. Боты постоянно ищут /phpmyadmin/, /pma/, /mysql/, /admin/pma/ — и находят. Если у вас phpMyAdmin доступен по стандартному URL без дополнительной защиты, это вопрос времени.
Первое — сменить URL. Второе — закрыть HTTP Basic Auth на уровне nginx. Третье — ограничить по IP. Вот конфиг nginx для этого:
server {
listen 443 ssl http2;
server_name example.com;
# Закрываем phpMyAdmin по IP и Basic Auth
location /secret-pma-url/ {
# Разрешаем только с офисного IP
allow 203.0.113.42;
allow 192.168.1.0/24;
deny all;
# Добавляем Basic Auth как второй слой
auth_basic "Database Admin";
auth_basic_user_file /etc/nginx/.htpasswd_pma;
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Блокируем стандартные URL phpMyAdmin
location ~* ^/(phpmyadmin|pma|mysql|adminer|dbadmin) {
deny all;
return 404;
}
}
Честно говоря, я всё чаще рекомендую клиентам вообще убирать phpMyAdmin с продакшн-сервера. Для разовых задач — SSH-туннель и локальный клиент (TablePlus, DBeaver, DataGrip). Это безопаснее любой веб-оболочки.
Если phpMyAdmin всё же нужен постоянно — хотя бы обновляйте его. В 2024-2025 годах было несколько серьёзных CVE. И включайте двухфакторную аутентификацию — про настройку 2FA читайте в статье Двухфакторная аутентификация на сайте: настройка 2026.
db_a7f3k9.php, и удаляйте после каждой сессии работы с БД.Защита от SQL-инъекций на уровне кода
Сетевая изоляция защищает от внешнего доступа к MySQL напрямую, но не защищает от SQL-инъекций через само приложение. Это принципиально разные векторы атаки. И если первый закрывается конфигурацией сервера, второй — исключительно качеством кода.
Правило простое: никогда не подставляйте пользовательские данные напрямую в SQL-запросы. Только параметризованные запросы или ORM. В PHP с PDO это выглядит так:
PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // Важно! Отключаем эмуляцию
PDO::MYSQL_ATTR_SSL_CA => '/etc/ssl/mysql/ca-cert.pem', // SSL
]
);
} catch (PDOException $e) {
// Логируем ошибку, но не показываем пользователю детали
error_log('DB connection failed: ' . $e->getMessage());
die('Ошибка подключения к базе данных');
}
// Параметризованный запрос
$stmt = $pdo->prepare('SELECT id, username, email FROM users WHERE id = :id AND status = :status');
$stmt->execute([
':id' => (int)$_GET['id'],
':status' => 'active'
]);
$user = $stmt->fetch();
// В Laravel — Eloquent ORM или Query Builder (оба безопасны)
$user = User::where('id', $request->id)->where('status', 'active')->first();
// Если нужен сырой запрос в Laravel — используем bindings
$users = DB::select('SELECT * FROM users WHERE email = ?', [$request->email]);
В WordPress ситуация немного другая — там есть глобальный объект $wpdb с методом prepare(). Но я видел десятки плагинов, которые игнорируют это и пишут сырые запросы. Поэтому при выборе плагинов всегда смотрю код — хотя бы по-диагонали.
Дополнительный уровень защиты — WAF (Web Application Firewall). Он фильтрует подозрительные запросы ещё до того, как они дойдут до PHP. Про настройку файрвола я подробно писал в статье Настройка Firewall для защиты сайта: полное руководство 2026.
Шифрование соединения с базой данных
Если приложение и БД на одном сервере — соединение идёт через Unix socket или localhost, шифрование не критично (трафик не покидает машину). Но если серверов несколько — шифруйте соединение обязательно.
MySQL 8.0 поддерживает SSL/TLS из коробки. Проверяем текущее состояние:
-- Проверяем, включён ли SSL
SHOW VARIABLES LIKE '%ssl%';
SHOW VARIABLES LIKE 'have_ssl';
-- Проверяем, использует ли текущее соединение SSL
SHOW STATUS LIKE 'Ssl_cipher';
Для генерации сертификатов и настройки SSL в MySQL 8.0 есть удобный способ через mysql_ssl_rsa_setup. Но я предпочитаю собственные сертификаты через Let's Encrypt или внутренний CA — так больше контроля.
[mysqld]
# Включаем SSL
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem
# Требуем SSL для конкретного пользователя
# (настраивается отдельно через GRANT ... REQUIRE SSL)
[client]
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/client-cert.pem
ssl-key=/etc/mysql/ssl/client-key.pem
И требуем SSL для пользователя приложения:
-- Требуем SSL для пользователя (MySQL 8.0)
ALTER USER 'mysite_user'@'%' REQUIRE SSL;
FLUSH PRIVILEGES;
-- Или при создании
CREATE USER 'mysite_user'@'%'
IDENTIFIED BY 'Str0ng!P@ssw0rd#2026'
REQUIRE SSL;
Аудит и логирование запросов к БД
Без логирования вы не узнаете об атаке до тех пор, пока не станет слишком поздно. Я настраиваю логирование на всех продакшн-проектах — это обязательный элемент.
В MySQL 8.0 есть несколько видов логов. General Query Log — логирует все запросы, но на нагруженном сайте это убьёт диск и производительность. Binary Log — нужен для репликации и point-in-time recovery. Error Log — обязателен всегда. И Slow Query Log — логирует медленные запросы, очень полезен для оптимизации.
[mysqld]
# Error log — всегда включён
log_error = /var/log/mysql/error.log
# Slow query log — запросы дольше 1 секунды
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
# Binary log — для репликации и восстановления
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
expire_logs_days = 7
# Аудит через плагин (MySQL Enterprise или Percona Audit)
# plugin-load-add = audit_log.so
# audit_log_file = /var/log/mysql/audit.log
# audit_log_format = JSON
# audit_log_policy = ALL
Для бесплатного аудита на MySQL Community Edition рекомендую Percona Audit Log Plugin — он бесплатный и пишет подробные JSON-логи всех соединений, запросов, изменений прав. Анализировать логи удобно через стек ELK или хотя бы через простые grep-скрипты в cron.
Про настройку мониторинга и анализа логов подробнее читайте в статье Настройка логов сайта: мониторинг и анализ ошибок в 2026 — там я разбираю весь стек от syslog до Grafana.
Защита файлов конфигурации и .env
Строка подключения к БД — это ключ от всего. Если злоумышленник получит данные из .env или wp-config.php, все остальные меры защиты теряют смысл. Я видел проекты, где .env лежал в корне сайта и был доступен по прямому URL. Это ещё хуже, чем открытый порт 3306.
Для nginx блокируем доступ к конфигурационным файлам:
server {
# Блокируем доступ к .env и другим конфигам
location ~ /\.(env|git|svn|htaccess|htpasswd) {
deny all;
return 404;
}
# Блокируем доступ к конфигам composer/npm
location ~ /(composer\.(json|lock)|package\.json|webpack\.config\.js) {
deny all;
return 404;
}
# Блокируем доступ к директориям с исходниками
location ~ ^/(vendor|node_modules|storage|bootstrap/cache) {
deny all;
return 404;
}
}
Для Apache / .htaccess:
# Блокируем .env
Order allow,deny
Deny from all
# Блокируем wp-config.php от прямого доступа (WordPress)
Order allow,deny
Deny from all
# Блокируем по расширению
Order allow,deny
Deny from all
Права на файлы тоже важны. .env должен иметь права 600 (только владелец читает и пишет). wp-config.php — 400 или 440. Директория /storage в Laravel — 755, но не 777. Это частая ошибка на shared-хостингах, где в панели одной кнопкой ставят 777 на всё подряд.
# Правильные права для Laravel
chmod 600 .env
chmod -R 755 storage bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache
# Для WordPress
chmod 400 wp-config.php
# Или если нужна запись (автообновления)
chmod 440 wp-config.php
Резервное копирование и план восстановления БД
Защита базы данных — это не только про предотвращение взлома, но и про готовность к худшему сценарию. Бэкапы — это ваша страховка. И они должны быть не просто настроены, но и проверены.
Я настраиваю автоматические бэкапы БД через cron с шифрованием и отправкой на внешнее хранилище:
#!/bin/bash
# /usr/local/bin/backup_db.sh
DB_NAME="mysite_db"
DB_USER="backup_user"
DB_PASS="BackupP@ss2026!"
BACKUP_DIR="/var/backups/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${DATE}.sql.gz"
ENCRYPTED_FILE="${BACKUP_FILE}.enc"
# Создаём директорию если не существует
mkdir -p $BACKUP_DIR
# Делаем дамп с сжатием
mysqldump \
--user=$DB_USER \
--password=$DB_PASS \
--single-transaction \
--routines \
--triggers \
--events \
$DB_NAME | gzip > $BACKUP_FILE
# Шифруем через openssl
openssl enc -aes-256-cbc -pbkdf2 -iter 100000 \
-in $BACKUP_FILE \
-out $ENCRYPTED_FILE \
-pass file:/etc/backup/db_backup.key
# Удаляем незашифрованный файл
rm $BACKUP_FILE
# Отправляем в S3-совместимое хранилище (например, Yandex Object Storage)
aws s3 cp $ENCRYPTED_FILE s3://my-backups/mysql/ \
--endpoint-url https://storage.yandexcloud.net
# Удаляем локальные бэкапы старше 7 дней
find $BACKUP_DIR -name "*.enc" -mtime +7 -delete
echo "Backup completed: $ENCRYPTED_FILE"
Про полную настройку резервного копирования, расписания и проверку целостности бэкапов я подробно писал в статье Настройка автоматического резервного копирования сайтов 2026 — там разобраны все нюансы, включая проверку восстановления.
Отдельно подчеркну: бэкап без проверки восстановления — это иллюзия безопасности. Раз в месяц разворачивайте бэкап на тестовом окружении и убеждайтесь, что данные целы. Я знаю случай, когда компания делала бэкапы 2 года, а при реальном инциденте оказалось, что mysqldump падал с ошибкой из-за прав доступа — и все файлы были пустыми.
Специфические настройки для WordPress, Битрикс и Laravel
Разные CMS требуют разных подходов к защите БД. Расскажу о ключевых моментах для каждой платформы.
WordPress. Меняйте префикс таблиц. По умолчанию это wp_, и все атакующие это знают. При установке выбирайте случайный префикс, например x7k2m_. Если сайт уже установлен — в плагинах типа Better WP Security (iThemes Security) есть инструмент для смены префикса. Также используйте отдельные константы в wp-config.php для ограничения прав на редактирование файлов из админки — это уменьшает поверхность атаки.
Битрикс. В Битриксе есть встроенный модуль безопасности — используйте его. Включите защиту от SQL-инъекций в настройках главного модуля. Регулярно запускайте сканер безопасности из панели администратора. И обязательно читайте Как обновить Битрикс без потери данных — устаревшая версия Битрикса это огромная дыра в безопасности.
Laravel. Используйте только Eloquent или Query Builder — никаких DB::statement() с пользовательскими данными. Настройте разные соединения для чтения и записи (read/write splitting) — это не только производительность, но и безопасность: пользователь с правами только на SELECT физически не сможет изменить данные через read-реплику. И обязательно шифруйте чувствительные данные в БД через встроенный Crypt-фасад Laravel.
Если вам нужна профессиональная проверка безопасности вашего сайта или настройка всего описанного под ключ — посмотрите на поддержку сайтов на Битрикс или поддержку WordPress. Мы проводим полный аудит безопасности и настраиваем защиту по всем описанным пунктам.
Чек-лист защиты базы данных: что проверить прямо сейчас
Резюмирую всё вышесказанное в практический список. Пройдитесь по каждому пункту на своих проектах — это займёт несколько часов, но может спасти от серьёзных последствий.
- MySQL слушает только localhost (127.0.0.1), порт 3306 закрыт на файрволе
- Для каждого сайта — отдельный пользователь БД с минимальными правами
- Пароль пользователя БД — минимум 16 символов, случайный, хранится в .env с правами 600
- Анонимные пользователи MySQL удалены, тестовая база удалена
- phpMyAdmin недоступен по стандартному URL, закрыт по IP и Basic Auth
- .env и wp-config.php недоступны через веб (проверьте через браузер!)
- Весь код использует параметризованные запросы или ORM
- Включён Slow Query Log для мониторинга производительности
- Автоматические бэкапы БД настроены, зашифрованы, хранятся на внешнем хранилище
- Последний бэкап проверен на восстановление
- Версия MySQL актуальная (минимум 8.0.x с последними патчами)
- PHP версии 8.1 минимум, лучше 8.2 или 8.3 — старые версии имеют уязвимости
Грубо говоря, большинство взломов происходит не из-за каких-то экзотических уязвимостей нулевого дня, а из-за элементарных упущений: открытый порт, слабый пароль, устаревший phpMyAdmin. Закройте базовые дыры — и вы уже на голову выше большинства сайтов в интернете. А для более глубокой защиты всего периметра рекомендую изучить Как защитить сайт от взлома: 10 правил безопасности — там я разбираю тему комплексно, включая серверный уровень и мониторинг инцидентов.
Хотите надёжно защитить базу данных вашего сайта?
Свяжитесь с нами — настроим безопасный доступ, шифрование и мониторинг вашей БД под ключ.