Защита phpMyAdmin от брутфорса при помощи Fail2Ban
В предыдущей статье, мы рассмотрели методику защиты от подбора пароля, к панели управления сервером ISPmanager. Теперь когда базовая защита аккаунта у нас настроена, перейдем к защите одного из важнейших элементов веб-сервера phpMyAdmin. Очень часто взлом сайтов, происходит по вине вебмастера, поленившегося поставить длинный и сложный пароль к БД MySQL. Подобрав имя пользователя и пароль, злоумышленник проникает в phpMyAdmin и как правило просто удаляет все БД к которым сумел добраться. Как правило, большинство phpMyAdmin защищены cookies аутентификаций, но при этом лог попыток авторизаций не ведется.
У многих возникает резонный вопрос, каким-же образом Fail2Ban сможет блокировать перебор паролей, если нет лога? Да вобщем совсем не сложно, даже для начинающего программиста или системного администратора. Все что нам понадобится, это базовые навыки PHP и немного внимательности. Итак начнем!
Логирование неудачных попыток входа в phpMyAdmin
Поскольку мы используем cookies аутентификацию, нам понадобится файл, в котором находятся функции cookies аутентификации, в CentOS он находится тут - /usr/share/phpMyAdmin/libraries/auth/cookie.auth.lib.php Копируем файл (на случай кривизны рук), далее открываем файл в текстовом редакторе и находим данную функцию:
function PMA_auth_fails()
{
global $conn_error;
// Deletes password cookie and displays the login form
$GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $GLOBALS['server']);
if (! empty($GLOBALS['login_without_password_is_forbidden'])) {
$conn_error = __('Login without a password is forbidden by configuration (see AllowNoPassword)');
} elseif (! empty($GLOBALS['allowDeny_forbidden'])) {
$conn_error = __('Access denied');
} elseif (! empty($GLOBALS['no_activity'])) {
$conn_error = sprintf(__('No activity within %s seconds; please log in again'), $GLOBALS['cfg']['LoginCookieValidity']);
// Remember where we got timeout to return on same place
if (PMA_getenv('SCRIPT_NAME')) {
$GLOBALS['target'] = basename(PMA_getenv('SCRIPT_NAME'));
// avoid "missing parameter: field" on re-entry
if ('tbl_alter.php' == $GLOBALS['target']) {
$GLOBALS['target'] = 'tbl_structure.php';
}
}
} elseif (PMA_DBI_getError()) {
$conn_error = '#' . $GLOBALS['errno'] . ' ' . __('Cannot log in to the MySQL server');
} else {
$conn_error = __('Cannot log in to the MySQL server');
}
// needed for PHP-CGI (not need for FastCGI or mod-php)
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
PMA_auth();
}
И добавляем в функцию внесколько строк, которые собственно и добавят, в системный лог, сообщения о неудачных попытках авторизации в phpMyAdmin. B результате этих нехитрых манипуляций, мы получим следующий код:
function PMA_auth_fails(){
global $conn_error;
// Deletes password cookie and displays the login form
$GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $GLOBALS['server']);
if (! empty($GLOBALS['login_without_password_is_forbidden'])) {
$conn_error = __('Login without a password is forbidden by configuration (see AllowNoPassword)');
$info_auth_error = 'ERROR AUTH phpMyAdmin (password empty)';
} elseif (! empty($GLOBALS['allowDeny_forbidden'])) {
$conn_error = __('Access denied');
$info_auth_error = 'ERROR AUTH phpMyAdmin (access denied)';
} elseif (! empty($GLOBALS['no_activity'])) {
$conn_error = sprintf(__('No activity within %s seconds; please log in again'), $GLOBALS['cfg']['LoginCookieValidity']);
$info_auth_error = NULL;
// Remember where we got timeout to return on same place
if (PMA_getenv('SCRIPT_NAME')) {
$GLOBALS['target'] = basename(PMA_getenv('SCRIPT_NAME'));
// avoid "missing parameter: field" on re-entry
if ('tbl_alter.php' == $GLOBALS['target']) {
$GLOBALS['target'] = 'tbl_structure.php';
}
}
} elseif (PMA_DBI_getError()) {
$conn_error = '#' . $GLOBALS['errno'] . ' ' . __('Cannot log in to the MySQL server');
$info_auth_error = 'ERROR AUTH phpMyAdmin (cannot log in to the MySQL server)';
} else {
$conn_error = __('Cannot log in to the MySQL server');
$info_auth_error = 'ERROR AUTH phpMyAdmin (cannot log in to the MySQL server)';
}
// needed for PHP-CGI (not need for FastCGI or mod-php)
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');
if(!empty($info_auth_error)){
openlog("phpMyAdmin", LOG_PID | LOG_PERROR, LOG_LOCAL0);
syslog(LOG_WARNING, $_SERVER['REMOTE_ADDR']." - ".$info_auth_error);
closelog();
}
PMA_auth();
}
Теперь загружаем файл на сервер и пробуем авторизоваться в phpMyAdmin, указав некорректное имя пользователя или пароль. Далее если вы все сделали правильно, в системном логе, которые находится (для CentOS) по адресу - /var/log/messages мы получим запись, примерно такого вида (где хxx.xxx.xxx.xxx ваш IP адрес):
Apr 26 13:36:02 p150170 phpMyAdmin[21516]: xxx.xxx.xxx.xxx - ERROR AUTH phpMyAdmin
Теперь в данном логе, у нас будут фиксироваться, все неудачные попытки авторизации в phpMyAdmin. Можно приступать к настройке Fail2Ban, создав фильтр и правило блокировки. Заходим в директорию - /etc/fail2ban/filter.d/ и создаем там файл - phpmyadmin.conf
Открываем созданный нами файл для редактирования и вносим в него следующее правило:
[Definition] failregex = <HOST> - ERROR AUTH phpMyAdmin ignoreregex =
Настраиваем файл конфигурации
Открываем файл конфигурации Fail2Ban - /etc/fail2ban/jail.conf и вносим в него правило блокировки, для защиты phpMyAdmin. Указываем число неудачных попыток авторизации - 10, время блокировки злоумышленника - 3 часа (10800 секунд). Время блокировки и количество неудачных авторизаций, стоит подбирать самостоятельно, это строго индивидуально
[phpmyadmin] enabled = true filter = phpmyadmin action = iptables-multiport[name=phpMyAdmin, port="80,443", protocol=tcp] logpath = /var/log/messages findtime = 600 maxretry = 10 bantime = 10800
Проверяем корректность настройки
# fail2ban-regex /var/log/messages /etc/fail2ban/filter.d/phpmyadmin.conf
Перезапускаем Fail2Ban
# service fail2ban restart
В результате этих нехитрых манипуляций, все попытки подбора паролей к phpMyAdmin будут заканчиваться баном злоумышленника, а администратор сервера, сможет спокойно спать по ночам :)