Ограничение количества попыток ввода пароля в веб-форме авторизации при помощи Nginx или HAProxy на примере WordPress

    Рассмотрим на примере WordPress способ усиления безопасности при помощи ограничения количества HTTP-запросов к форме ввода пароля. Это позволит оградить опубликованный блог от брутфорса (поиска и взлома пароля путем перебора всех теоретически возможных вариантов из определенного набора символов или подбора по словарю распространенных паролей). Данный способ, в принципе, можно использовать и для защиты других веб-приложений.

    Задача может быть реализована в Nginx с помощью модуля ngx_http_limit_req_module [1], выступающем в роли фронт-энда к Apache или веб-сервера FastCGI, или же с помощью HAProxy [2, 3], выступающем в роли балансировщика нагрузки перед веб-серверами.

    В обоих случаях алгоритм работы следующий. При аутентификации браузер обращается по адресу, содержащему в себе подстроку "/wp-login.php". Необходимо отследить ее и ограничить количество запросов с одного IP не затрагивая обращения по всем остальным адресам. Параметры блокировки необходимо подобрать таким образом, чтобы не создавать неудобств обычным пользователями. Особенно внимательно следует настраивать блокировки в том случае, когда формой авторизации пользуется большое количество пользователей с одного IP-адреса.

    Способ №1: Nginx

    Конфигурационный файл исправлен по советам из комментариев. Спасибо VBart и J_o_k_e_R!

    http {
         <...>
         
         limit_req_zone $binary_remote_addr zone=login:10m  rate=15r/m;
    
         server {
              listen      80;
              server_name  frontend.example.org;
    
         location ~* /wp-login.php {
              limit_req  zone=login burst=4;
              proxy_pass        http://backend:8080;
              <...>
         }
    
         location / {
              proxy_pass        http://backend:8080;
              <...>
         }
    }
    

    Параметры блокировки:

    limit_req_zone $binary_remote_addr zone=login:10m rate=15r/m; Задаёт параметры зоны разделяемой памяти, которая хранит состояние для разных IP-адресов. В нашем случае состояния хранятся в зоне “login” размером 10 мегабайт, и средняя скорость обработки запросов для этой зоны не может превышать 15 запросов в минуту. Скорость обработки можно задать в запросах в секунду (r/s) или в запросах в минуту (r/m).

    limit_req zone=login burst=4; устанавливает зону login и максимальный размер всплеска запросов (burst). Если скорость поступления запросов превышает описанную в зоне, то их обработка задерживается так, чтобы запросы обрабатывались с заданной скоростью. Избыточные запросы задерживаются до тех пор, пока их число не превысит максимальный размер всплеска. При превышении запрос завершается с ошибкой 503.

    Способ №2: HAProxy

    В секцию backend, обслуживающую наш блог, добавляем следующие строки [2]:
    tcp-request inspect-delay 10s
    tcp-request content accept if HTTP
    # brute force protection
    acl wp_login path_beg -i /wp-login.php
    stick-table type binary len 20 size 500 store http_req_rate(20s) peers local
    tcp-request content track-sc2 base32+src if METH_POST wp_login
    stick store-request base32+src if METH_POST wp_login
    acl bruteforce_detection sc2_http_req_rate gt 5
    acl flag_bruteforce sc1_inc_gpc0 gt 0
    http-request deny if bruteforce_detection flag_bruteforce
    

    При обнаружении POST-запроса к к странице /wp-login.php сохраняется хэш из трех элементов: заголовка HTTP Host, URL-пути и IP источника. Идентифицируемый на основе хеша пользователь сможет сделать пять запросов за 20 секунд; шестой запрос будет заблокирован.

    Литература
    1. Модуль ngx_http_limit_req_module — nginx.org
    2. wordpress CMS brute force protection with HAProxy — blog.haproxy.com
    3. Better Rate Limiting For All with HAProxy — blog.serverfault.com

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 30

      +4
      location / {
                     if ( $request_uri ~* /wp-login.php) {
      ....
      

      Что это за лапша?!
      Почему не
      location /wp-login.php {
      limit_req  zone=login burst=4;
      proxy_pass        http://backend:8080;
      <...>
      }
      
        –2
        В примере с Nginx можно защитить любую подстроку, не зная наперед в корне или подкаталоге она располагается. Этим конфигурационным файлом не только wordpress закрыть можно.

        И да, наверное стоило в конфигурационном примере для haproxy сделать так же, воспользовавшись path_sub вместо path_beg.
          +2
          Я спрашивал зачем там if
          wiki.nginx.org/IfIsEvil
            0
            Если расскажите как иначе подружить location ~* с proxy_pass, буду очень благодарен.
              +1
              savostin прав. Вам следует убрать if и сделать, основываясь на вырезке из документации
              Если location задан регулярным выражением.

              В этом случае директиву следует указывать без URI.

              так (привожу пример из своей работающей на nginx-1.6.2 конфигурации, приведите сами к своей):
              upstream oldsites {
                      server 127.0.0.1:8081;
              }
              location ~* \.php$ {
                      try_files $uri =404;
                      proxy_pass http://oldsites;
              }
              
                0
                Очень интересно, спасибо! Обязательно попробую на стенде.
                  +1
                  Директиву proxy_pass без URI следует указывать всегда, когда подмена URI не требуется. Чтобы потом проблем не было.

                  Есть разница даже между:

                   location / {
                       proxy_pass http://backend;
                   }
                  

                  и

                   location / {
                       proxy_pass http://backend/;
                   }
                  

                  В последнем случае осуществляется дополнительная обработка, помимо бессмысленной замены "/" на "/", также происходит декодирование всего URI. Но документацию же мало кто читает внимательно, поэтому последняя конструкция весьма распространена.
                    0
                    Спасибо! Пост исправлен. If'ов в конф. файле больше нет.

                    Я упустил момент, в котором nginx начал позволять использовать proxy_pass в location ~*. Только что проверил на тестовом стенде, 1.6.2 спокойно это переваривает. Люблю сообщество как раз за такие вещи!
              –2
              Тут фокус в том и заключается, что для location ~* нельзя указать proxy_path напрямую.
                0
                а зачем там ~*?
                  0
                  ответил в комментарии выше.
                    –1
                    Неа, не ответили.
                    Вам в данном случае не нужен в location regexp, вообще.
                      +1
                      Право, эта дискуссия ставит меня в тупик.

                      Попробую объяснить более абстрактно. Предположим, у нас есть веб-сервер Apache (или группа веб серверов), что обслуживает, опять же предположим, домашние странички пользователей: /~username1 и /~username2. Перед ним установлен Nginx. У обоих (в общем случае N) пользователей установлена популярная CMS cmsname. У username1 в корне, у username2 в подкаталоге /superblog/. За авторизацию пусть отвечает login.php.

                      Конфигурацией вида location ~* /login.php мы с легкостью поймаем обе формы авторизации. Ваш же пример /location login.php — не сработает ни у одного из пользователей.
              +1
              Пост исправлен. If'ов в конф. файле больше нет.

              Я упустил момент, в котором nginx начал позволять использовать proxy_pass в location ~*. Однако, как подсказали в комментариях и я только что проверил на тестовом стенде, 1.6.2 спокойно это переваривает. Люблю сообщество как раз за такие вещи!
              0
              На мой взгляд у вордпресса проблем с безопасностью больше по темкам/плагинам, а не с брутфорсом…


              Это только за январь
                0
                Если заменить в статье "/wp-login.php" на "/user/login", то защищать будем Drupal. Если на "/administrator/" — то Joomla. Можно ещё 10 разных CMS привести. Wordress для примера взят, как самый популярный.
                0
                За такой nginx.conf обычно приговаривают к расстрелу. =) Смотреть: events.yandex.ru/lib/talks/2392/
                  0
                  Если расскажите как иначе подружить location ~* с proxy_pass, буду очень благодарен.
                    +4
                    А в чем проблема?

                    location ~* /wp-login.php {
                        proxy_pass http://backend:8080;
                    }
                    
                      0
                      Ха! Видимо уже ни в чем. На стенде заработало. Спасибо!

                      На одной из более ранних версий nginx оно не срабатывало. Вот, правда, не помню на чем именно — на proxy_pass или на limit_req

                      Дополню-ка пост:)
                        +3
                        На одной из более ранних версий nginx оно не срабатывало.
                        Вы видимо один из первых пользователей nginx? =)

                        nginx.org/ru/CHANGES.ru
                        Изменения в nginx 0.1.25                                          19.03.2005
                        
                            ...
                        
                            *) Добавление: директива proxy_pass может использоваться в location,
                               заданных регулярным выражением.
                        

                        Директива же limit_req могла использоваться в любых location всегда.
                          0
                          Ах, точно нет! :)

                          Если вдруг сохранился в резервных копиях старый стенд, посмотрю, что была за ошибка.
                    0
                    А можно как-то защитить от брутфорса http-auth?
                    0
                    У меня этим занимается fail2ban, например, его тоже можно влёт настроить, причем для всех сайтов шаред хостинга, и сразу для многих cms, дописав нужный путь в правило фильтра.
                      0
                      Задача может быть реализована в Nginx с помощью модуля ngx_http_limit_req_module [1], выступающем в роли фронт-энда к Apache или веб-сервера FastCGI.


                      Подскажите пожалуйста, как задача может быть реализована при использовании nginx в качестве nginx: т.е. статика + php-fpm через fastcgi_pass?
                        0
                        Уточню, как быть с цепочками:
                        location ~* /wp-login.php {
                          limit_req  zone=login burst=4;
                          fastcgi_pass unix:/var/php-fpm.sock;
                        }
                        
                        location ~ \.php?$ {
                          fastcgi_pass unix:/var/php-fpm.sock;
                        }
                        

                        — всё ли верно?
                          0
                          Да, все так. В данном случае совершенно неважно, куда дальше идет трафик — на Апач или на fcgi-сокет: limit_eq действует на определенную секцию «location»
                            0
                            Да, это здорово.
                            Но ведь после секции «location ~* /wp-login.php»
                            стоит секция с более общим регулярным выражением, которое включает в себя и файл wp-login.php — «location ~ \.php?$»
                            Могу ошибаться — но разве секция location с RegEx срабатывает только при первом совпадении с RequestedURI? Мне кажется — с каждым. Нет?
                            В этом и озадаченность.

                      Only users with full accounts can post comments. Log in, please.