fail2ban - Защита сервера от брутфорс атак на базовую HTTP авторизацию и другие сервисы

archive view archive save

article Как защитить сервер от брутфорс атак на базовую HTTP авторизацию. Для этого есть fail2ban, с помощью которого можно обезопасить почти любой веб сервер на базе Linux, lighttpd, Nginx, Apache и т.п..

Просматривая лог файл веб-сервера обнаружил множественные записи типа "[Sun Oct 13 17:38:31 2013] [error] [client 178.125.155.63] user Admin not found: /administrator/, referer: http://example.com/administrator/", которые чередовались с разных ИП по 5-7 записей каждые 5-10 сек - вероятно, что брутфорс на базовую HTTP авторизацию выполняется через какой-то ботнет.

Читаю лог.файл и сам себе думаю "Вот знать бы кто этот "добрый человек", да по братцки бы ему подсказать, что нет у нас такого юзера Admin":)) Ну, да ладно - пусть себе долбит базовую HTTP авторизацию до морковкина заговенья;), а нам по факту нужно подумать чем бы её прикрыть.

Можно написать своё решение на Shell-e, но зачем парится если уже есть готовое - fail2ban. fail2ban анализирует системные журналы различных сервисов и при наличии в них записей определённого содержания может выполнять некоторые действия, например бан ИП в брандмауэре.

fail2ban поддерживает такие сервисы как sshd, apache, qmail, proftpd, sasl, asterisk, etc и способен выполнять действия iptables, tcp-wrapper, shorewall, mail notifications, etc - этот список может быть легко расширен.

Процесс установки расписывать особого смысла нет ибо он хорошо расписан на оф. странице MANUAL 0 8 - Fail2ban, разве только не помешает пояснить некоторые моменты конфигурации.

Конфигурация Fail2ban

Основными файлами конфигурации являются /etc/fail2ban/fail2ban.conf и /etc/fail2ban/jail.conf, но их лучше не трогать и вместо них использовать /etc/fail2ban/fail2ban.local и /etc/fail2ban/jail.local соответственно - ниже приведена конфигурация для анализа неудачных попыток базовой HTTP авторизации на веб сервере apache (фильтр apache-auth, действие iptables):

vi /etc/fail2ban/fail2ban.local
 
[Definition]
 
# Option:  loglevel
# Notes.:  Set the log level output.
#          1 = ERROR
#          2 = WARN
#          3 = INFO
#          4 = DEBUG
# Values:  NUM  Default:  3
#
loglevel = 3
 
# Option:  logtarget
# Notes.:  Set the log target. This could be a file, SYSLOG, STDERR or STDOUT.
#          Only one log target can be specified.
#          If you change logtarget from the default value and you are
#          using logrotate -- also adjust or disable rotation in the
#          corresponding configuration file
#          (e.g. /etc/logrotate.d/fail2ban on Debian systems)
# Values:  STDOUT STDERR SYSLOG file  Default:  /var/log/fail2ban.log
#
logtarget = /var/log/fail2ban.log
 
-----------------------
 
vi /etc/fail2ban/jail.local
 
[DEFAULT]
# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not
# ban a host which matches an address in this list. Several addresses can be
# defined using space separator.
ignoreip = 127.0.0.1/8
 
# "bantime" is the number of seconds that a host is banned.
bantime  = 600
 
# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime  = 600
 
# "usedns" specifies if jails should trust hostnames in logs,
#   warn when DNS lookups are performed, or ignore all hostnames in logs
#
# yes:   if a hostname is encountered, a DNS lookup will be performed.
# warn:  if a hostname is encountered, a DNS lookup will be performed,
#        but it will be logged as a warning.
# no:    if a hostname is encountered, will not be used for banning,
#        but it will be logged as info.
usedns = no
 
 
# This jail corresponds to the standard configuration in Fail2ban 0.6.
# The mail-whois action send a notification e-mail with a whois request
# in the body.
 
[ssh-iptables]
 
enabled  = false
filter   = sshd
action   = iptables[name=SSH, port=ssh, protocol=tcp]
           sendmail-whois[name=SSH, dest=root, sender=fail2ban@example.com]
logpath  = /var/log/secure
maxretry = 5
 
 
# Deny http in iptables
 
[apache-iptables]
 
enabled  = true
filter   = apache-auth
action   = iptables[name=HTTP, port=http, protocol=tcp]
logpath  = /var/www/*/logs/error_log*
maxretry = 2
 
-----------------------
 
service fail2ban restart

ВНИМАНИЕ! Параметр ignoreip не рекомендуется оставлять со значением по умолчанию127.0.0.1/8, в многопользовательских системах это может создать очевидную угрозу — если злоумышленник хотя–бы к одному shell–аккаунту получит доступ, то он заимеет возможность запустить прямо с этого–же сервера bruteforce–программу для атаки на пользователя root или остальных пользователей системы. Желательно указать туда реальный ИП сервера, на котором стоит fail2ban. В директиве filter разрешено использовать только один фильтр!

Опция findtime — определяет продолжительность времени в секундах, за которое событие должно будет повториться определённое количество раз maxretry, после чего действие (бан) будет выполнено. Если параметр findtime не определён, то по умолчанию будет установлено равное 600 (10 мин.). Другими словами, если при maxretry равным 5, bruteforce–р проверит 4 пароля, затем выждет 10 минут, а потом проверит ещё 4 пароля, затем повторит снова и снова, то в таком случае его IP не будет забанен, а если в течении findtime превысит maxretry, то попадёт в бан. Значения по умолчанию лучше изменить имхо обычно некоторые "грамотные" ботнеты сконфигурированы с учетом значений по умолчанию для различных fail2ban-ов.

В logpath можно указать несколько лог файлов или путей-шаблонов, каждый с новой строки в формате:

logpath  = /var/www/*/logs/error_log*
           /var/log/apache*/error_log*

В приведённом выше примере конфигурации мы отключили (enabled = false) "изолятор" (Jail) [ssh-iptables] и добавили активный [apache-iptables], который по умолчанию отсутствует в /etc/fail2ban/jail.conf.

Есть изолятор [apache-tcpwrapper] с действием /etc/fail2ban/action.d/hostsdeny.conf, который при срабатывании правила блокирует ИП посредством записи "ALL: xxx.xxx.xxx.xxx" в файл /etc/hosts.deny, но недостатком этого метода является фронт сервер не поддерживающий "TCP Wrapper". Примером такого сервера является Nginx, который в целях лучшей производительности по умолчанию не поддерживает "TCP Wrappers Configuration Files", а как следствие и Nginx не читает файлы /etc/hosts.allow и /etc/hosts.deny. TCP Wrapper модуль для Nginx на github.com

Теперь немного о самих фильтрах/правилах и применяемых дейстиях...

Файлы (.conf) фильтров/правил расположены в каталоге /etc/fail2ban/filter.d/, а файлы (.conf) действий в /etc/fail2ban/action.d/. В нашем случае мы использовали фильтр /etc/fail2ban/filter.d/apache-auth.conf и действие /etc/fail2ban/action.d/iptables.conf. В некоторых версиях/репозиториях фильтры могут различаться и быть неработоспособными, например в нашем случае фильтр /etc/fail2ban/filter.d/apache-auth.conf вовсе не срабатывал на наличие в лог. файле Апача записей типа "... user Admin not found ..." и имел такую кондицию:

[Definition]
 
# Option:  failregex
# Notes.:  regex to match the password failure messages in the logfile. The
#          host must be matched by a group named "host". The tag "<HOST>" can
#          be used for standard IP/hostname matching and is only an alias for
#          (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values:  TEXT
#
failregex = ^%(_apache_error_client)s user .* (authentication failure|not found \
    |password mismatch)\s*$

Не работает из-за "\s*$" того, что по такому правилу сразу после "user .* not found" предполагается наличие только множественных пробелов с завершением строки, а в реальности после определения ещё идёт и ": /administrator/". Работоспособной будет такая дефиниция фильтра:

cp /etc/fail2ban/filter.d/apache-auth.conf /etc/fail2ban/filter.d/apache-auth.local
vi /etc/fail2ban/filter.d/apache-auth.local
 
[Definition]
 
# Option:  failregex
# Notes.:  regex to match the password failure messages in the logfile. The
#          host must be matched by a group named "host". The tag "<HOST>" can
#          be used for standard IP/hostname matching and is only an alias for
#          (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values:  TEXT
#
failregex = [[]client <HOST>[]] user .* authentication failure
            [[]client <HOST>[]] user .* not found
            [[]client <HOST>[]] user .* password mismatch
            [[]client <HOST>[]] ModSecurity: .* "Request Missing an Accept Header"
            [[]client <HOST>[]] ModSecurity: .* "GET or HEAD Request with Body Content."
            [[]client <HOST>[]] ModSecurity: .* "HTTP header is restricted by policy"
 
# либо
failregex = ^%(_apache_error_client)s user .* (authentication failure|not found\
    |password mismatch)
            ^%(_apache_error_client)s ModSecurity: .* ("Request Missing an Accept Header"|\
                "Request Missing a User Agent Header"|\
                "Request Missing a Host Header"\
                "GET or HEAD Request with Body Content."|\
                "HTTP header is restricted by policy")

Заодно мы сюда домазали ещё и строку поиска сообщений ModSecurity про "Request Missing an Accept Header" отсутствие в запросе клиента HTTP заголовка "Accept" и другие "Request Missing ...". Отсутствие в запросе клиента HTTP заголовка "Accept" и прочих "Request Missing ..." требуемых/обязательных HTTP заголовков присуще всяческим голимым ботам, сканерам и другим "мазокакерским" утилитам - такие пусть ходят мимо. Правда в их число могут попадать полезные сервисы/боты, например сервис проверки доступности сайта, но думаю, что ради одного сервиса не стоит ставить под удар весь сервер. Без HTTP заголовка "Accept" также могут приходить некоторые запросы с некоторых серверов GOOGLE (66.249.78.33), но обычно GOOGLE бродит по сайту с посылом HTTP заголовка "Accept" и "User-Agent", да и в панели гугль для веб-мастера жалоб не было.

Более того у этого 66.249.78.33 ИП принадлежащего GOOGLE, судя по запросам, далеко не мирные цели, пример обращения к нашему сайту:

# Реальный URL:
http://remoteshaman.com/index.php?option=com_content&view=article& \
    id=25:architecture-and-standards-for-the-unix-operating-system \
    &catid=24&Itemid=503
 
[Tue Oct 15 11:02:26 2013] [error] [client 66.249.78.33] ModSecurity: Access den
ied with code 403 (phase 2). Pattern match "\\\\W{4,}" at ARGS:id. [file "/etc/h
ttpd/modsecurity.d/activated_rules/modsecurity_crs_40_generic_attacks.conf"] [li
ne "37"] [id "960024"] [rev "2"] [msg "Meta-Character Anomaly Detection Alert -
Repetative Non-Word Characters"] [data "Matched Data: :----- found within ARGS:i
d: 25:-----unix"] [ver "OWASP_CRS/2.2.8"] [maturity "9"] [accuracy "8"] [hostnam
e "remoteshaman.com"] [uri "/index.php"] [unique_id "Ulzogl2qgHIAAEAeGXIAAAAE"]

Видим, что ИП 66.249.78.33 запросил аргумент (ARGS) "id" с его значением "25:-----unix", а из "Реальный URL" это выглядит так "id=25:architecture-and-standards-for-the-unix-operating-system" - т.е. до слова "unix" в реальном URL присутствует 5 символов "-". GOOGLE-бот изуродовал аргументы URL-а и попытался его запросить, что меня не сильно удивляет - GOOGLE довольно агрессивный и изобретательный бот, который сканирует такие URL-ы, которых на сайте в реальности не существует, например /ru/|/fr/|/en/ и т.п., чем иногда может хорошо подвесить сайт. Поэтому на его запросы без HTTP заголовка "Accept", думаю, можно "забить", да и как уже упоминалось в панели гугль для веб-мастера жалоб на проблемы сканирования не было обнаружено.

При желании можно запилить разрешающее ModSecurity правило для User-Agent, но нужно иметь ввиду, что User-Agent легко подделывается!

SecRule REQUEST_HEADERS:User-Agent "\+http:\/\/www\.google\.com\/bot\.html" \
    "phase:1,nolog,allow,ctl:ruleEngine=Off"
 
# или что-то типа:
SecRule REMOTE_ADDR "! @ pmFromFile /path/to/whitelist.txt" \
    "pass,ctl:removeRuleById:960015"
 
# или что-то типа:
SecRule REMOTE_ADDR "@pmFromFile /etc/asl/whitelist" "nolog,phase:1,allow"
vi /etc/asl/whitelist
echo "10.11.12.13" >> /etc/asl/whitelist

Для ModSecurity можно создать отдельный изолятор и фильтр соответственно, например /etc/fail2ban/filter.d/apache-modsec.local...

Исключить ИП адреса нужных ботов можно по CIDR маске в ignoreip:

vi /etc/fail2ban/jail.local
# http://www.ipaddressguide.com/cidr
# GOOGLE CIDR: 173.194.0.0/16 74.125.0.0/16 66.249.64.0/19
# YANDEX CIDR: 213.180.204.0/24 77.88.21.0/24 95.108.138.0/24
ignoreip = 127.0.0.1/8 173.194.0.0/16 74.125.0.0/16 66.249.64.0/19 \
    213.180.204.0/24 77.88.21.0/24 95.108.138.0/24

Ещё можно включить бан по наличии частых сообщений "File does not exist", что может свидетельствовать о сканировании, но перед этим желательно включить модуль mod_speling и активировать его директивой "CheckSpelling on", а иначе может быть много ложных срабатываний из-за написания УРЛ-а в разном регистре - т.е. если есть файл или каталог с именем http://example.com/vasya, то при обращении по ссылке http://example.com/Vasya мы получим 404 "File does not exist"!

FEATURE Split config - Fail2ban Любые изменения вносимые пользователем рекомендуется вносить в файлы имя_файла.local, а не в имя_файла.conf - это касается всех конфигурационных файлов, в т.ч. и файлов фильтров из /etc/fail2ban/filter.d/. Это делается с целью сохранить изменения после обновления!

Настройка действия для iptables довольно проста, в квадратных скобках [...] нужно указать параметры для команды/правила iptables. "action = iptables[name=HTTP, port=http, protocol=tcp]" - "name=CHAIN" имя цепочки (будет выглядеть "iptables -L|grep -i chain" как "fail2ban-CHAIN"), "port=http" имя или номер порта, "protocol=tcp" имя протокола соответственно. Основной шаблон формирования правила для iptables расположен в /etc/fail2ban/action.d/iptables.conf.

Большой плюс что фильтры/правила можно проверить:

fail2ban-regex /root/tmp/test.log \
    '[[]client <HOST>[]] ModSecurity: .* "Request Missing an Accept Header"'
 
Success, the total number of match is 47419

Также можно выяснить или изменить текущий статус изолятора:

fail2ban-client status apache-iptables
 
Status for the jail: apache-iptables
|- filter
|  |- File list:        /var/www/dd/logs/error_log_main
|  |- Currently failed: 270
|  `- Total failed:     2622
`- action
   |- Currently banned: 320
   |  `- IP list:       178.127.82.25 46.48.126.82 176.36.194.97 77.41.7.126 178
......................
   `- Total banned:     900

Для ручной разблокировки IP адреса используйте "fail2ban-client set <JAIL> unbanip <IP>", например "fail2ban-client set apache-iptables unbanip 178.127.82.25", подробнее в "man fail2ban-client".

Известные проблемы с Fail2ban

Fail2ban на реагирует на событие - если показалось что правило не срабатывает, но его проверка с помощью "fail2ban-regex ... ..." проходит успешно, тогда выполните "fail2ban-client reload", а если не помогло, то стоит проверить лог файлы и заносятся ли туда события. При использовании "Piped logging program to rotate Apache logs" возможны временные задержки в регистрации событий.

После ротации Fail2ban начинает писать в SYSLOG, а не как установлено в /var/log/fail2ban.log. Это мелкая ошибка в версии 0.8.10, исправлено в версии v0.8.10.dev. Приставка .dev говорит, что эта версия заведомо может быть нестабильной.

Для исправления самостоятельно нужно подправить /etc/logrotate.d/fail2ban и перезапустить сервис:

vi /etc/logrotate.d/fail2ban
 
# До (неправильно)
/var/log/fail2ban.log {
    missingok
    notifempty
    size 30k
    create 0600 root root
    postrotate
        /usr/bin/fail2ban-client set logtarget SYSLOG 2> /dev/null || true
    endscript
}
 
# После (правильно)
/var/log/fail2ban.log {
    missingok
    notifempty
    size 1M
    create 0600 root root
    postrotate
        /usr/bin/fail2ban-client reload 2> /dev/null || true
    endscript
}

Мы также увеличили размер лог.файла до 1M ибо 30k это уж слишком маловато будет. Рекомендуется не обновляться до версий .dev или Pre-release, а использовать только Latest release! Releases · fail2ban/fail2ban

Чем в итоге нам полезен Fail2ban

Хорошо иметь базовую авторизацию в админ интерфейсе под прикрытием Fail2ban и сейчас я расскажу почему...

Кроме массы "добрых" людей по сети ещё бродит оч. много различных ботнетов, которые щупают стандартные каталоги admin|administrator|phpmyadmin etc.., нащупав которые начинают ломать до морковкина заговенья. Кроме попыток "брутфорса" базовой HTTP авторизации они могут предпринимать попытки взлома/спама на другие части сайта, а когда они получают "отлуп" с последующей отправкой в "бан-ю" iptales-a, то на протяжении времени бана остальные части им будут недоступны, а значит мы сэкономим на количестве подключений к сокету, что в общей сложности добавит нашему "серванту" стабильности и безопасности.

Поэтому, даже если у нас нет каталога /phpmyadmin/|/admin/|/administrator/, то его просто необходимо создать, добавить на него базовую HTTP авторизацию и прикрыть fail2ban-ом - получится некое подобие HONEYPOT-а :)

Как видим защита от брутфорса базовой HTTP авторизации при помощи Fail2ban может быть довольно эффективной, а в комплексе с другими средствами, такими как ModSecurity, является мощным средством для полномасштабной защиты сервера от различной дряни, которое помогает сэкономить на подключениях, а соответственно и на системных ресурсах (ЦП, РАМ, место на HDD).

Бот подолбил базовую HTTP авторизацию на сервере с "[Sun Oct 13 17:38:31 2013]" до "[Mon Oct 14 19:29:17 2013]", а потом пропал с концами - видимо боту как-то дошло, что гиблое это дело и здесь он может обломать себе зубы :)

Как ранее упоминалось, у медали всегда две стороны - по идее легальные поисковые боты (GOOGLE/Yandex/MSN etc) недолжны обращаться к сайту методом POST и в запросе должны отправлять HTTP заголовок "Accept" и "User-Agent", но на практике бывает всё иначе, а поэтому иногда они могут попадать в бан под Fail2ban или ModSecurity - ИП ботов можно добавить в исключения. В результате возможно временное снижение ПР (ака "Page Rank"), позиций в выдаче и т.д., но, нужно выбирать - либо стабильность и безопасность сервера либо ПР (ака "Page Rank") и позиции в результатах поиска.

Fail2ban можно заточить/допилить почти под любые запросы/сервисы. Все описанные здесь примеры приведены на примере собственного опыта, часть которых успешно используются на данном сервере - поэтому "непидрулите" к нашему сайту без HTTP заголовка "Accept" и "User-Agent", не пытайтесь "брутфорсить" базовую HTTP авторизацию и ведите себя хорошо, а иначе будете добровольно-принудительно, как минимум на пару часов, сосланы в "баню"!;)

Олег Головский


Комментарии в блоге
Новое на форуме