MySQL является одной из самых развернутых баз данных в мире, что делает ее одной из самых целевых. Многие установки MySQL работают с настройками по умолчанию, чрезмерно разрешительными учетными записями пользователей и отсутствием шифрования. Некоторые из них являются устройствами, которые случайно попали в производство. Другие — это производственные системы, которые никто никогда не затвердел, потому что «это за брандмауэром»
Эта статья охватывает восемь реальных ошибок безопасности, которые оставляют открытыми базы данных MySQL. Не абстрактные модели угроз, а конкретные ошибки, которые злоумышленники действительно ищут и используют.
1. Работа с учетными данными по умолчанию и корневой учетной записью
Это звучит очевидно, но все равно происходит постоянно. Свежие установки MySQL часто поставляются с корневой учетной записью, которая не имеет пароля или известного пароля по умолчанию. Автоматизированные сканеры специально ищут экземпляры MySQL на порту 3306 с пустыми корневыми паролями. Требуются секунды, чтобы найти и использовать.
Корневая учетная запись в MySQL имеет неограниченный доступ ко всему: всем базам данных, всем таблицам, всем административным командам. Использование его для подключения приложений означает, что ваше приложение имеет полный контроль над сервером, включая возможность сбрасывать базы данных, создавать пользователей и изменять гранты.
Исправьте корневой пароль сразу после установки:
| |
Затем создайте отдельные учетные записи для каждого приложения только с необходимыми привилегиями:
| |
Ограничение на 10.0.1.% означает, что этот пользователь может подключаться только из подсети приложения. Если кто-то крадет учетные данные, они не могут использовать их с произвольной машины.
Запустите mysql_secure_installation на каждом новом экземпляре MySQL. Он удаляет анонимных пользователей, отключает удаленный вход в root и сбрасывает тестовую базу данных. Это занимает тридцать секунд и закрывает наиболее распространенные векторы атаки.
2. Предоставление чрезмерных привилегий пользователям приложений
Большинство приложений MySQL нуждаются в SELECT, INSERT, UPDATE и DELETE в определенных базах данных. Вот так. Тем не менее, часто можно увидеть учетные записи приложений с GRANT ALL PRIVILEGES ON *.*, потому что кто-то скопировал ответ Stack Overflow во время первоначальной настройки и никогда не пересматривал его.
Ущерб от чрезмерных привилегий масштабируется с уровнем доступа. Учетная запись приложения с привилегией FILE может читать любой файл, к которому может получить доступ процесс MySQL в файловой системе сервера. PROCESS позволяет видеть все запущенные запросы, в том числе от других пользователей. SUPER позволяет убивать соединения и изменять глобальные переменные.
Проверяйте текущие гранты, чтобы увидеть, что на самом деле назначено:
| |
Если пользователь вашего приложения появляется в этом списке, что-то не так. Отмените то, что вам не нужно:
| |
Хорошее эмпирическое правило: если вы не можете объяснить, почему учетная запись нуждается в определенной привилегии, она не должна иметь ее.
3. Размещение MySQL в интернете без сетевых ограничений
По умолчанию MySQL прослушивает все сетевые интерфейсы. Это означает, что если ваш сервер имеет публичный IP-адрес, MySQL доступен из всего Интернета. В сочетании со слабыми учетными данными (ошибка No1) происходит большинство нарушений MySQL.
Проверьте свою текущую привязку:
| |
Если он показывает 0.0.0.0 или *, MySQL принимает соединения отовсюду.
Ограничьте это в my.cnf:
| |
Это ограничивает MySQL только локальными соединениями. Если ваше приложение работает на другом сервере, свяжитесь с интерфейсом частной сети:
| |
Но одной только сетевой связи недостаточно. Добавьте правила брандмауэра для ограничения порта 3306:
| |
Также отключите опции skip-networking и skip-name-resolve задумчиво. skip-networking полностью отключает соединения TCP (работают только соединения сокетов), что нормально, если приложение находится на одном хосте. Решение skip-name-resolv предотвращает поиск DNS для подключения хостов, что ускоряет соединения и удаляет спуфинг DNS в качестве вектора атаки.
Если ваше приложение должно достичь MySQL через Интернет, используйте туннель SSH или VPN вместо открытия порта 3306 напрямую. Никогда не разоблачайте MySQL в общедоступном Интернете, даже с надежными паролями.
4. Не шифровать соединения с TLS
Соединения MySQL по умолчанию передают данные в простом тексте. Это включает в себя запросы, наборы результатов, имена пользователей и пароли. Любой, кто может захватить сетевой трафик между вашим приложением и MySQL, может прочитать все.
Это не просто теоретическая проблема. На виртуальном хостинге, облачных VPC с неправильно настроенными группами безопасности и корпоративными сетями, обнюхивание пакетов представляет собой реальную угрозу. Даже частные сети не всегда так изолированы, как вы думаете.
Проверьте, включен ли TLS:
| |
Чтобы включить TLS, создать или получить сертификаты и настроить MySQL:
| |
Требование require_secure_transport = ON настройки заставляет все соединения использовать TLS. Без него клиенты могут подключаться незашифрованными.
Вы также можете применять TLS для каждого пользователя, что полезно для постепенного развертывания:
| |
Убедитесь, что соединения действительно зашифрованы:
| |
И со стороны клиента:
| |
Если Ssl_cipherr возвращает пустую строку, соединение не шифруется.
5. Оставляя двоичный журнал и каталог данных незащищенными
Бинарный журнал MySQL содержит каждое заявление об изменении данных, которое работает против базы данных. Если злоумышленник получает доступ к файловой системе, он может прочитать двоичный журнал и восстановить всю историю данных: каждую вставку, обновление и удаление.
Сам каталог данных содержит фактические файлы таблицы. В зависимости от двигателя хранения они могут быть читаемыми с помощью основных инструментов. Файлы InnoDB можно анализировать с помощью специализированных утилит для извлечения необработанных данных, полностью обходя аутентификацию MySQL.
Проверьте текущие разрешения файлов:
| |
Каталог данных MySQL и каталог журналов должны принадлежать пользователю и группе mysql без каких-либо разрешений для чтения по всему миру:
| |
Также защитите конфигурационный файл MySQL, который может содержать пароли:
| |
Если вы используете MySQL в Docker, убедитесь, что объемные крепления для данных и журналов не доступны для чтения по всему миру в файловой системе хоста. Разрешения по умолчанию Docker могут быть более разрешительными, чем вы ожидаете.
В частности, для двоичного журнала рассмотрите возможность его шифрования. MySQL 8.0+ поддерживает шифрование двоичного журнала:
| |
Это шифрует двоичные файлы журнала в состоянии покоя. Даже если кто-то копирует файлы, они не могут прочитать содержимое без ключа шифрования.
6. Игнорирование SQL-инъекций в коде приложения
SQL-инъекция была вектором атаки базы данных номер один в течение более двух десятилетий, и она по-прежнему работает, потому что разработчики продолжают создавать запросы, соединяя пользовательский ввод непосредственно в строки SQL. MySQL не имеет встроенной защиты от этого. Защита должна исходить от кода приложения.
Инъекционный запрос выглядит так:
| |
Если user_input является ' OR '1'='1' --, запрос становится:
| |
Это возвращает каждую строку в таблице пользователей. Более разрушительные полезные нагрузки могут сбрасывать таблицы, читать файлы с диска (если у пользователя MySQL есть привилегия FILE) или создавать новые учетные записи администратора.
Это параметризированные запросы. Каждая библиотека баз данных поддерживает их:
| |
| |
| |
Параметризованные запросы отделяют структуру SQL от данных. Движок базы данных знает, что параметр является значением, а не кодом SQL, независимо от того, что он содержит.
Со стороны MySQL вы можете уменьшить радиус взрыва, удалив привилегию FILE из учетных записей приложений (см. ошибку #2) и запустив MySQL с --local-infile=0, чтобы отключить LOAD DATA LOCAL INFILE, который злоумышленники используют для чтения файлов через SQL-инъекцию.
7. Отсутствие аудита или мониторинга доступа к базам данных
Если кто-то обращается к вашей базе данных MySQL так, как они не должны, как быстро вы узнаете? Большинство установок MySQL не имеют возможности регистрации аудита. Злоумышленник может читать чувствительные таблицы в течение нескольких недель, прежде чем кто-либо заметит.
MySQL Enterprise Edition включает в себя плагин для аудита, но для этого требуется другой подход. Общий журнал запросов является одним из вариантов, хотя он захватывает все и создает огромные файлы журналов на загруженных серверах.
Более практичный подход к выпуску сообщества заключается в том, чтобы обеспечить конкретную регистрацию:
| |
Для мониторинга соединения регулярно проверяйте, кто подключен и что они делают:
| |
Отслеживание неудачных попыток входа, проверив журнал ошибок. Повторяющиеся неудавшиеся логины из того же IP обычно означают, что происходит атака грубой силы.
Для производственных систем рассмотрите возможность развертывания стороннего плагина аудита, такого как audit_log от Percona или плагин аудита MariaDB (который работает с вилками MySQL). Они обеспечивают структурированные, фильтруемые аудиторские маршруты без накладных расходов на общий журнал запросов.
Настройка оповещений о критических событиях: создание новых пользователей, изменения привилегий, подключения от неожиданных хостов и запросы к чувствительным таблицам. Цель состоит в том, чтобы обнаружить необычную активность, прежде чем она станет полным нарушением.
8. Пропуск резервных копий или их безопасное хранение
Безопасность — это не только предотвращение несанкционированного доступа. Речь также идет о восстановлении. Атаки на базы данных MySQL реальны: злоумышленники получают доступ, бросают все таблицы и оставляют записку о выкупе. Без подкрепления вы ведете переговоры с преступниками.
Но иметь резервные копии недостаточно, если они хранятся небезопасно. Незашифрованные файлы резервного копирования, находящиеся на том же сервере, что и MySQL, бесполезны в сценарии вымогателей, потому что злоумышленник также удаляет их. Резервное копирование на ведре S3 с открытым доступом к считыванию - это просто другой вид утечки данных.
Стратегия безопасного резервного копирования охватывает три вещи:
- Шифрование — резервные файлы должны быть зашифрованы в состоянии покоя, поэтому они бесполезны, если их украсть
- Внешнее хранение — По крайней мере одна копия должна быть в отдельной системе или облачном хранилище, к которому сервер MySQL не имеет удаленного доступа.
- Регулярное тестирование — резервная копия, которую вы никогда не восстанавливали, — это резервная копия, которая, как вы надеетесь, работает.
Для MySQL mysqldump является основным инструментом:
| |
Это создает сжатую, зашифрованную резервную копию. Но управление ключами шифрования, планирование и хранение за пределами сайта вручную утомительно и подвержено ошибкам.
Инструменты резервного копирования MySQL, такие как Databasus, автоматизируют весь процесс. Это отраслевой стандарт для инструментов резервного копирования MySQL, который обрабатывает планирование, сжатие, шифрование и хранение AES-256-GCM в нескольких направлениях, таких как S3, Google Drive и SFTP. Он подходит для отдельных разработчиков и корпоративных команд, с управлением доступом на основе рабочего пространства и журналами аудита.
Какой бы подход вы ни выбрали, убедитесь, что ваши резервные копии не доступны с сервера MySQL с теми же учетными данными. Если сервер базы данных скомпрометирован, злоумышленник не сможет удалить ваши резервные копии.
Модель, стоящая за этими ошибками
Рассматривая эти восемь ошибок вместе, возникает закономерность. Большинство сбоев в безопасности MySQL происходят из-за неизмененных по умолчанию разрешений, которые никогда не пересматривались и не отслеживались. Ни одно из этих исправлений не является сложным. Они не требуют дорогостоящих инструментов или глубоких знаний в области безопасности.
Начните с основ: сильные учетные данные, минимальные привилегии, сетевые ограничения и зашифрованные соединения. Затем добавьте мониторинг, чтобы вы знали, когда происходит что-то необычное. И продолжайте тестировать зашифрованные резервные копии, чтобы вы могли восстановиться, когда профилактика не удается.
Лучшее время для защиты базы данных MySQL было, когда вы ее создали.