Перейти к основному содержанию
Блог

Серверы

Безопасность базы данных: от порта до шифрования полей

База не должна быть открыта в интернет; пользователи с минимальными правами, TLS, резервные копии, шифрование чувствительных полей и аудит. Практика для PostgreSQL и MySQL.

· 11 мин. чтения

Фото: The National Archives (UK), CC BY 3.0 · Wikimedia Commons

База данных — самая ценная часть системы. Пользователи, платежи, документы, персональные данные — всё хранится здесь. Тем не менее базу часто устанавливают по принципу «приложение работает, значит всё в порядке»: настройки по умолчанию, один пользователь root или postgres, прослушивание на всех сетевых интерфейсах.

В этой статье разберём основные слои защиты базы: сеть, пользователи и права, шифрованное соединение, шифрование чувствительных полей, резервное копирование и аудит. Примеры — для PostgreSQL и MySQL/MariaDB, сторона приложения — на примере Laravel и Node.

В чём проблема

Самые частые ошибки с базами данных:

  • Порт открыт в интернет. MySQL (3306), PostgreSQL (5432), Redis (6379), MongoDB (27017) видны снаружи. Это прямой путь для подбора паролей и эксплуатации уязвимостей в самой СУБД.
  • Приложение подключается под суперпользователем. Если в приложении используется root или postgres, одна SQL-инъекция может дать управление всем сервером: другие базы, чтение файлов, выполнение команд.
  • Слабые или повторяющиеся пароли. Один и тот же пароль в нескольких средах, файл .env остался в истории git.
  • Нешифрованное соединение. Если приложение и база на разных серверах, запросы и результаты идут по сети открытым текстом.
  • Чувствительные поля хранятся открыто. Паспортные данные, ПИНФЛ, токены — обычным текстом. Любая копия базы — полная утечка.
  • Открыт веб-интерфейс базы данных. Инструменты вроде phpMyAdmin или Adminer доступны по публичному адресу.

Почему это важно для экспертизы

На экспертизе сервер сканируется снаружи по портам — открытый порт базы обнаруживается сразу. На экспертизе нашего собственного продукта открытые порты тоже были отдельной находкой (там это были порты FTP и панели), подробнее: открытые порты и харденинг сервера. Кроме того:

  • адреса вроде /phpmyadmin, /adminer.php, /pma проверяются по типичным именам;
  • если найдена SQL-инъекция, её последствия зависят от прав пользователя в базе — минимальные права ограничивают ущерб;
  • категория A02 «Cryptographic Failures» в OWASP Top 10 (2021) охватывает отсутствие шифрования при хранении и передаче чувствительных данных, а A05 «Security Misconfiguration» — настройки по умолчанию.

Принцип: база видна только приложению, приложение может делать только необходимое, а чувствительные данные нельзя прочитать, даже если они утекут из базы.

Проверьте сами

Где слушает база

Внутри сервера:

sudo ss -tlnp | grep -E "3306|5432|6379|27017"

127.0.0.1 или Unix-сокет — хорошо. 0.0.0.0 или * — при отсутствии firewall база открыта снаружи.

Снаружи (с другого компьютера, только для своего сервера):

nmap -Pn -p 3306,5432,6379,27017 your-server-ip

Пользователи и права

PostgreSQL:

SELECT rolname, rolsuper, rolcreaterole, rolcreatedb FROM pg_roles WHERE rolcanlogin;

MySQL:

SELECT user, host FROM mysql.user;
SHOW GRANTS FOR 'app'@'localhost';

У пользователя приложения не должно быть SUPER, FILE, GRANT OPTION или ALL PRIVILEGES ON *.*. Если в столбце host стоит %, пользователь может подключиться с любого адреса.

Веб-интерфейсы

for p in phpmyadmin pma adminer.php adminer phpMyAdmin; do
  printf "%s " "$p"; curl -s -o /dev/null -w "%{http_code}\n" "https://example.uz/$p/"
done

Исправление

1. Привяжите базу к локальному адресу

PostgreSQL postgresql.conf:

listen_addresses = 'localhost'

MySQL/MariaDB mysqld.cnf:

[mysqld]
bind-address = 127.0.0.1

Redis redis.conf: bind 127.0.0.1 ::1 и protected-mode yes, а также пароль или ACL.

Если база на отдельном сервере, она должна слушать только внутреннюю сеть, а firewall — разрешать доступ только с IP сервера приложения:

sudo ufw allow from 10.0.0.5 to any port 5432 proto tcp

В PostgreSQL по тому же принципу настраивается pg_hba.conf:

# TYPE  DATABASE  USER  ADDRESS        METHOD
local   appdb     app                  scram-sha-256
hostssl appdb     app   10.0.0.5/32    scram-sha-256

Не используйте метод trust, для хеширования паролей выберите scram-sha-256 (password_encryption = scram-sha-256).

2. Пользователи с минимальными правами

Разделите как минимум три роли: миграции (меняют схему), приложение (работает только с данными) и резервное копирование или отчёты (только чтение).

PostgreSQL:

CREATE ROLE app LOGIN PASSWORD '...';
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT USAGE ON SCHEMA public TO app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app;
ALTER DEFAULT PRIVILEGES FOR ROLE migrator IN SCHEMA public
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app;

MySQL:

CREATE USER 'app'@'localhost' IDENTIFIED BY '...';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'localhost';
CREATE USER 'report'@'localhost' IDENTIFIED BY '...';
GRANT SELECT ON appdb.* TO 'report'@'localhost';

Генерируйте пароли через openssl rand -base64 24 и храните их только в .env или менеджере секретов. Для каждой среды (тест, production) — свой пароль. Подробнее: управление секретами и ключами.

3. Шифрованное соединение (TLS)

Если база и приложение на одном сервере и подключение идёт через Unix-сокет или 127.0.0.1, TLS не обязателен. Если на разных — обязателен.

На сервере PostgreSQL настраиваются ssl = on и сертификат, в pg_hba.conf используется hostssl. Laravel config/database.php:

'pgsql' => [
    // ...
    'sslmode' => env('DB_SSLMODE', 'verify-full'),
],

В MySQL можно обязать пользователя подключаться только по TLS:

ALTER USER 'app'@'10.0.0.5' REQUIRE SSL;

Пример для Node.js (pg):

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: { ca: fs.readFileSync('/etc/ssl/db-ca.pem'), rejectUnauthorized: true },
});

rejectUnauthorized: false отключает проверку сертификата, и TLS теряет смысл.

4. Шифрование чувствительных полей

Шифрование диска защищает при краже сервера, но не защищает данные, полученные через дамп базы или SQL-инъекцию. Для самых чувствительных полей добавьте шифрование на уровне приложения.

В Laravel — cast encrypted:

protected function casts(): array
{
    return [
        'passport_number' => 'encrypted',
        'api_secret' => 'encrypted',
    ];
}

Если нужен поиск по зашифрованному полю, храните в отдельном столбце ключевой хеш (HMAC) и ищите по нему. Пароли же не шифруются, а хешируются через bcrypt или argon2id.

Важно: ключ шифрования (APP_KEY) хранится отдельно от базы. Если ключ и база утекут вместе, шифрование не поможет. План смены ключа тоже нужно продумать заранее.

5. Закройте веб-интерфейсы

Если phpMyAdmin или Adminer нужны, не оставляйте их по публичному адресу: только через SSH-туннель или allowlist. Лучше всего вообще не устанавливать их на production-сервер, а при необходимости подключаться локальным клиентом через SSH-туннель:

ssh -L 5432:127.0.0.1:5432 user@your-server

6. Резервные копии и аудит

Резервная копия базы должна быть зашифрована и храниться в другом месте, а восстановление — регулярно проверяться; подробно об этом в статье резервные копии и восстановление.

Для аудита:

  • в PostgreSQL log_connections = on, log_disconnections = on, для изменений схемы log_statement = 'ddl'; для детального аудита — расширение pgaudit;
  • в MySQL — логирование неудачных подключений, в Percona или MariaDB — плагин аудита;
  • на уровне приложения — кто просматривал или изменял чувствительную запись (таблица аудита).

Логирование всех запросов (log_statement = 'all') занимает много места и приводит к попаданию чувствительных данных в логи — включайте его только для временной диагностики.

7. Обновления

СУБД тоже нужно обновлять. Версии с истёкшим сроком поддержки не получают исправлений безопасности. Проверьте версию (SELECT version();) и сверьте её с графиком поддержки производителя.

На стороне приложения

Настройки базы не могут полностью закрыть ошибку в приложении. Основные правила:

  • только параметризованные запросы, никакой конкатенации значений в whereRaw и сыром SQL;
  • allowlist для orderBy и имён столбцов;
  • не показывать SQL-запросы и детали базы в сообщениях об ошибках (APP_DEBUG=false);
  • никогда не помещать строку подключения во front-end или мобильное приложение.

Короткий чек-лист

  • Порты базы закрыты из интернета, база слушает 127.0.0.1 или внутреннюю сеть
  • pg_hba.conf / host в MySQL разрешают только нужные адреса, нет trust
  • Приложение подключается не под суперпользователем, а под пользователем с минимальными правами
  • Отдельные роли для миграций, приложения и отчётов
  • Сильные пароли, свои для каждой среды; .env нет в git
  • TLS и проверка сертификата между разными серверами
  • Самые чувствительные поля зашифрованы на уровне приложения, ключ хранится отдельно
  • phpMyAdmin/Adminer не доступны по публичному адресу
  • Зашифрованные резервные копии и тест восстановления
  • Подключения и изменения схемы логируются, версия СУБД поддерживается

Итог

Защита базы состоит из нескольких простых слоёв: скрыть её из сети, ограничить права, зашифровать соединение и чувствительные поля, делать копии и вести наблюдение. Каждый слой ограничивает ущерб, если другой не сработал. В нашем процессе Claude AI агент просматривает конфигурацию базы, pg_hba.conf, права пользователей и запросы в коде приложения, а специалист применяет изменения в безопасном порядке, не останавливая работающую систему.

Если хотите узнать, готова ли ваша база к экспертизе, оставьте заявку на сканирование. Мы поможем закрыть уязвимости до официальной проверки.