Модуль nginx для борьбы с DDoS

    Многие сталкивались с таким явлением как DDoS атака методом HTTP флуда. Нет, это не очередной туториал по настройке nginx, хочу представить свой модуль, работающий как быстрый фильтр между ботами и бэкэндом во время L7 DDoS атаки и позволяющий отсеивать мусорные запросы.

    Модуль умеет


    • Ставить cookies стандартным способом через HTTP заголовок Set-Cookie. После установки cookies перенаправлять пользователя (заставить отправить нам полученные cookies) используя код ответа 301 и заголовок Location
    • После установки cookies перенаправлять пользователя (заставить отправить нам полученные cookies) используя код ответа 200 и HTML тег Meta «refresh»
    • Считать количество попыток поставить cookies и отправлять пользователя по заданному URL после превышения максимального количества неудачных попыток
    • Использовать custom шаблоны для ответа фильтра, в которых можно делать что угодно (например, выставить cookies через JavaScript)
    • Для предотвращения автоматического парсинга ответов нацеленных на исполнение JavaScript, шифровать значение переменных в шаблоне симметричным криптоалгоритмом с дальнейшей расшифровкой через JavaScript на стороне клиента (используя SlowAES)
    • Whitelist'ить заданные сети (например, сети в которых живут поисковые роботы)
    • Кое-какие мелкие фишки полезные во время DoS атаки.

    Не умеет


    • Модуль лишь возвращает клиенту заданные ответы, принимать решение о блокировке клиента (например, используя fail2ban) вы должны самостоятельно
    • Кто-то скажет — «Я сэмулирую JavaScript», но давайте будем реалистами — часто вас DoS'ят ботами с полноценной эмуляцией? отправьте их ко мне, будем майнить bitcoin'ы
    • В документации ничего нет про captcha и flash — если нужно, вы можете прикрутить их самостоятельно, нужно лишь проявить фантазию при конфигурации
    • Данный модуль не панацея — это лишь маленький компонент в комплексе защитных мер, инструмент, который может помочь, если его правильно использовать.

    Как это работает


    Чаще всего, боты, реализующие HTTP флуд довольно тупые, и не имеют механизмов HTTP Cookie и редиректа. Иногда попадаются более продвинутые — такие могут использовать cookies и обрабатывать редиректы, но почти никогда DoS бот не несет в себе полноценного JavaScript движка.
    Для понимания принципа работы фильтра ниже приведен flow общения клиент-сервер в зависимости от сценария атаки.

    1. Боты не понимают редиректы и cookies

    2. Боты понимают редиректы и cookie, но не умеют JavaScript



    Примеры конфигурации для основных сценариев атак


    • боты не понимают редиректы и cookies (типичный случай)
      server {
          listen 80;
          server_name domain.com;
      
      
          testcookie off;
          testcookie_name BPC;
          testcookie_secret keepmescret;
          testcookie_session $remote_addr;
          testcookie_arg attempt;
          testcookie_max_attempts 3;
          testcookie_fallback /cookies.html?backurl=http://$host$request_uri;
          testcookie_get_only on;
      
      
          location = /cookies.html {
              root /var/www/public_html;
          }
      
          location / {
              testcookie on;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_pass http://127.0.0.1:8080;
          }
      }
      
    • боты понимают редиректы и cookies
      server {
          listen 80;
          server_name domain.com;
      
          testcookie off;
          testcookie_name BPC;
          testcookie_secret keepmescret;
          testcookie_session $remote_addr;
          testcookie_arg attempt;
          testcookie_max_attempts 3;
          testcookie_fallback /cookies.html?backurl=http://$host$request_uri;
          testcookie_get_only on;
          testcookie_redirect_via_refresh on;
          testcookie_refresh_template '<html><body><script>document.cookie="BPC=$testcookie_set";document.location.href="$testcookie_nexturl";</script></body></html>';
      
          location = /cookies.html {
              root /var/www/public_html;
          }
      
      
          location / {
              testcookie on;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_pass http://127.0.0.1:8080;
          }
      }
      
    • защищаемый URL вставили в iframe на популярном сайте
      server {
          listen 80;
          server_name domain.com;
      
      
          testcookie off;
          testcookie_name BPC;
          testcookie_secret keepmescret;
          testcookie_session $remote_addr;
          testcookie_arg attempt;
          testcookie_max_attempts 3;
          testcookie_fallback /cookies.html?backurl=http://$host$request_uri;
          testcookie_get_only on;
          testcookie_redirect_via_refresh on;
          testcookie_refresh_template '<html><body><script>function bla() { document.cookie="BPC=$testcookie_set";document.location.href="$testcookie_nexturl";}</script><input type="submit" value="click me" onclick="bla();"></body></html>';
      
          location = /cookies.html {
              root /var/www/public_html;
          }
      
          location / {
              testcookie on;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_pass http://127.0.0.1:8080;
          }
      }
      
    • боты научились вытаскивать значение cookies через regexp
      server {
          listen 80;
          server_name domain.com;
      
          testcookie off;
          testcookie_name BPC;
          testcookie_secret keepmescret;
          testcookie_session $remote_addr;
          testcookie_arg attempt;
          testcookie_max_attempts 3;
          testcookie_fallback /cookies.html?backurl=http://$host$request_uri;
          testcookie_get_only on;
          testcookie_redirect_via_refresh on;
      
          testcookie_refresh_encrypt_cookie on;
          testcookie_refresh_encrypt_cookie_key random;
          testcookie_refresh_encrypt_cookie_iv random;
          testcookie_refresh_template '<html><body>setting cookie...<script type=\"text/javascript\" src=\"/aes.min.js\" ></script><script>function toNumbers(d){var e=[];d.replace(/(..)/g,function(d){e.push(parseInt(d,16))});return e}function toHex(){for(var d=[],d=1==arguments.length&&arguments[0].constructor==Array?arguments[0]:arguments,e="",f=0;f<d.length;f++)e+=(16>d[f]?"0":"")+d[f].toString(16);return e.toLowerCase()}var a=toNumbers("$testcookie_enc_key"),b=toNumbers("$testcookie_enc_iv"),c=toNumbers("$testcookie_enc_set");document.cookie="BPC="+toHex(slowAES.decrypt(c,2,a,b))+"; expires=Thu, 31-Dec-37 23:55:55 GMT; path=/";document.location.href="$testcookie_nexturl";</script></body></html>';
      
          location = /aes.min.js {
              gzip on;
              gzip_min_length 1000;
              gzip_types text/plain;
              root /var/www/public_html;
          }
      
          location = /cookies.html {
              root /var/www/public_html;
          }
      
          location / {
              testcookie on;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_pass http://127.0.0.1:8080;
          }
      }
      
    • боты научились вытаскивать параметры через regexp и расшифровывать значение cookies (сомневаюсь, что кто-то будет возиться)
      server {
          listen 80;
          server_name domain.com;
      
          testcookie off;
          testcookie_name BPC;
          testcookie_secret keepmescret;
          testcookie_session $remote_addr;
          testcookie_arg attempt;
          testcookie_max_attempts 3;
          testcookie_fallback /cookies.html?backurl=http://$host$request_uri;
          testcookie_get_only on;
          testcookie_redirect_via_refresh on;
          testcookie_refresh_encrypt_cookie on;
          testcookie_refresh_encrypt_cookie_key deadbeefdeadbeefdeadbeefdeadbeef; #меняем по крону
          testcookie_refresh_encrypt_cookie_iv deadbeefdeadbeefdeadbeefdeadbeef; #меняем по крону
      
          testcookie_refresh_template '<html><body>setting cookie...<script type=\"text/javascript\" src=\"/aes.min.js\" ></script><script>function toNumbers(d){var e=[];d.replace(/(..)/g,function(d){e.push(parseInt(d,16))});return e}function toHex(){for(var d=[],d=1==arguments.length&&arguments[0].constructor==Array?arguments[0]:arguments,e="",f=0;f<d.length;f++)e+=(16>d[f]?"0":"")+d[f].toString(16);return e.toLowerCase()}var a=toNumbers({используем любимый JS обфускатор чтобы спрятать значение iv, меняем по крону}),b=toNumbers({используем любимый JS обфускатор чтобы спрятать значение ключа, меняем по крону}),c=toNumbers("$testcookie_enc_set");document.cookie="BPC="+toHex(slowAES.decrypt(c,2,a,b))+"; expires=Thu, 31-Dec-37 23:55:55 GMT; path=/";document.location.href="$testcookie_nexturl";</script></body></html>';
      
          location = /aes.min.js {
              gzip on;
              gzip_min_length 1000;
              gzip_types text/plain;
              root /var/www/public_html;
          }
      
          location = /cookies.html {
              root /var/www/public_html;
          }
      
          location / {
              testcookie on;
              proxy_set_header Host $host;
              proxy_set_header X-Real-IP $remote_addr;
              proxy_pass http://127.0.0.1:8080;
          }
      }
      


    Исходные тексты

    Модуль с инструкциями по установке и документацией доступен на github под BSD лицензией.
    Патчи, дополнения, тесты и баг-репорты приветствуются.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 43
    • +4
      Интересный модуль, обязательно протестирую!
      Буквально вчера в 23:50 закончилась DDoS атака на один из ресурсов который я администрирую :)

      От атаки удалось отбиться средствами iptables (ограничение на кол-во одновременных коннектов с одного IP + ограничение на connection rate с одного IP) + кеширование атакуемых страниц.

      Всерьёз начинал думать про защиту через cookies, но атака к этому момент уже закончилась (правда не в виде модуля, а через встроенный perl).
      • +3
        сейчас нет времени и возможностей провести нагрузочное тестирование, но было бы интересно, насколько % изменяется производительность nginx с данным модулем и без него.

        для встроенного perl даже есть аналогичный модуль, но он не спасет от парсинга контента regexp'ом + нужно вкомпиливать поддержку perl.
      • +9
        С радостью бы потестировал. Ни у кого нет ботнета, на пару дней погонять?
        • +13
          Имеется небольшой (около 100Мб/сек) — пишите в личку. Только как-нибудь подтвердите, что сервер ваш, а то мало ли что…
        • +3
          прямо сейчас идёт ддос атака, будем тестировать
          • 0
            Выдаёт 403 Forbidden. Настройки использую 1 в 1 из первого примера, nginx 1.1.4. ЧЯДНТ?
            • 0
              давайте ваш контакт в почту, secret выставили? что в логах?
              • +3
                сообщество потом уведомите чего и как, ок?
                • +3
                  проблема была в том, что nginx собран с поддержкой ipv6
                  • +2
                    спасибо за тест — ошибка исправлена, теперь можно оставлять поддержку IPv6.
            • –8
              По-моему, проще за 15-30 минут накопить статистику ботов и на firewall'e делать ресет соединения для этих IP/network.
              Поведение ботов очень сильно отличается от поведения обычных посетителей.
              • +1
                за это время вам могут положить сервер
                • –1
                  Нет. LA незначительно успеет повысится.
                  А дальше скрипт в автоматическом режиме банит, без участия человека.
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • –1
                      LA за какой промежуток времени? :)
                      1? 5? или 15 минут?
                      • 0
                        Каждый день, вижу ддосы, которые за 1 минуту до 1500, LA за текущий момент, который высчитывается средним за одну минуту.
                        Вы не правы и все тут :)
                    • +1
                      смотря ка кбудут досить и как написан ресурс
                      завалить можно и за секунды

                      а если быть правльнее то лучше это дело выносить на апаратный фаер, но не все могут себе такое позволить
                      • 0
                        >Нет. LA незначительно успеет повысится.

                        Слабенькие на Вас атаки.
                    • +2
                      Конкретно вчера имел дело с DDOS'ом, в моём случае блокировал IP адреса каждые 5 минут (по логам nginx'а), практически не помогало — новые адреса появлялись практически в течении всего времени атаки, в большинстве случаев время «активной» жизни каждого адреса исчислялось единицами минут (потом смотрел по счетчикам на iptables).
                      И было всего порядка 100 IP адресов, с которых атака шла постоянно.
                      • 0
                        Медленно блокировали.
                        На VDS iptables с большими списками перестает работать, жалуется на нехватку памяти.
                        • 0
                          На VPS (виртуалка, причём на чём-то вроде openVZ/Virtuozzo) или действительно на VDS (выделенный сервер)?
                          В первом случае понятно — хостер поставил ограничения за пределы которых вы не можете выйти.
                          Но во втором -очень странно. Имея полноценные рутовые права можно совершенно спокойно увеличить максимальный размер таблицы iptables. Другой вопрос, что если надо блокировать много IP адресов и не требуется потом отслеживать активность каждого конкретного адреса, то есть значительно более эффективные способы.
                    • +2
                      Правильное направление, удачи вам в дальнейшей разработке! Многие очень недооценивают возможности кук при борьбе с ддосом, а с их помощью можно отбивать достаточно мощные аттаки. Ну по крайней мере не дать сайту сложится какое-то время и хоть как, но отвечать на нормальные запросы.
                      • +2
                        Да, красивое решение. Желаю успехов в развитии!
                        • +5
                          Очень часто всякие лоики не ставят user agent, в nginx это можно использовать себе на руку:

                          if ($http_user_agent = ''") {
                          return 403;
                          }
                          • +2
                            иногда работает, но это все-таки грабли ;)
                            • +8
                              тьфу, костыли, а не грабли.
                          • +1
                            а что будем делать с full browser stack?
                            • +3
                              когда доля DoS ботов с full browser stack перевалит за 30%, можно будет озаботиться капчей, но я выше описал свои мысли на тему — «мы выполним JavaScript».
                            • +4
                              captcha это сразу -30% посетителей. как ни странно боты капчу лучше отгадывают чем большинство граждан этой страны. true story ;)

                              идея заставить клиента делать существенные вычисления на своей стороне чтобы уменьшить плечо атаки, это вполне разумно. вот только одна загвоздка — что делать когда клиентов ОЧЕНЬ много (десятки, а возможно и сотни тысяч)?

                              стоит-ли лезть в прилжение и создавать всем клиантам, в том числе и хорошим, такие хлопоты? если можно
                              ограничить клиента по количеству запросов написав тривиальный частотный анализатор запросов, и отлавливая буратин по распределению. работает, вообще без проблем если не забывать про ajax
                              • +1
                                статистический анализатор, ограничения L4 уровня никто не отменял — всё это необходимо, модуль выполняет другую задачу — не дает лечь бэкэндам, пока анализаторы собирают статистику и отсеивают подозрительных.
                                всё можно варьировать, и капчу можно показывать только тем кто слишком активен и прочее и прочее, но согласитесь, это должно быть в инфраструктуре, коммерческом решении, здесь же предлагается инструмент, который поможет не по всех, но во многих тривиальных случаях.
                                если он заставит авторов ботнетов потратить время и силы на разработку — profit! ;)
                                • +1
                                  Да, безусловно это профит! Тут спору нет и за этот профит вам спасибо большое и карма++.
                                  Я просто предлагаю возможности развития вашей идее. Набор инструментов лучше одной отвертки.
                                  • +1
                                    в случае с FBS будет эффективно соместить частотный и поведенческий анализ + немного интерактива.
                                    • 0
                                      а где подписаться на новости вашей разработки?
                                      • 0
                                        к сожалению(счастью?), я сейчас не занимаюсь разработками в этой области, просто наконец-то дошли руки привести в порядок старые наработки и поделиться с людьми.
                              • 0
                                Это все круто, если атака «детская».
                                А если это ДДОС на сетевую структуру емкостью под 40Гбит\с, то вам даже никто защититься не даст — вас отрубят аплинки.
                                • +6
                                  Такой DDoS достаточно безопасен. Как правило это вариации на тему udp flood, иногда с использованием какой-нибудь оригинальной техники XYZ amplification. Резать полосу по битмаске — тут никакого кунгфу не надо.
                                  Да, атаки разрушительны но они достаточно краткосрочны — несколько десятков часов максимум. Вот хорошо поставленный L7 flood с FBS и мутацией который проходит сквозь Arbor как нож сквозь масло и который нельзя в принципе сигнатурой порезать — вот это TRUE SKILL. И продолжаться это может месяцами. Размер ущерба сами можете представить ;)

                                  • 0
                                    Резать полосу по битмаске — тут никакого кунгфу не надо
                                    Математику тут простая, чтоб эти 40Ж не пришли к вам, они должны прийти к кому-то другому, в любом случае они будут проходит через провайдера, который в определенный момент перестанет вас анонсировать.
                                  • +3
                                    Бинт — это круто, если порезали палец, а если оторвало руку — то уже не поможет, факт. Но к чему это?
                                  • 0
                                    Turbid: даже не знаю что ответить. Вещи-же очевидные. Контракты на много полосы у множества провайдеров.
                                    • 0
                                      >После установки cookies перенаправлять пользователя (заставить отправить нам полученные cookies) используя код ответа 200 и HTML тег Meta «refresh

                                      Сильно неправильный метод:
                                      * поисковики не смогут  индексировати такую страницу, роботы социальных сетей не смогут вытягивать анонсы ваших статей, за что SEOшники защищаемого вами сайта будут вам «премного благодарны»
                                      * при достаточно распространенной настройке firefox и explorer вместо переадресации будут орать о том что «свершается непоправимое»

                                      Но а вообще модуль дельный, стоит пилить дпльше…
                                      • 0
                                        Каждый сам решает, включать эту опцию или нет, для поисковиков есть whitelist'инг, для RSS можно прописать исключения.
                                        И еще — подобная техника используется в одном из режимов Cisco Guard.
                                        • 0
                                          Ваш скрипт помог преодолеть серьезный DDoS, только вот whitelist почему-то не хочет работать :(
                                          • +1
                                            Прошу прощения за предыдущее сообщение. Все наладили и все работает отлично.
                                            Еще раз спасибо за модуль!

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.