Как известно DDoS атаки на сайт бывают разной интенсивности, имеет значение количество хостов участвующих в атаке, количество сетевых пакетов и объем передаваемых данных. В самых тяжелых случаях отбить атаку возможно только применяя специализированное оборудование и сервисы.
Если же объем атаки меньше пропускной способности сетевого оборудования и вычислительных мощностей сервера (пула серверов) обслуживающих сайт, то можно попробовать “заглушить” атаку не прибегая к сторонним сервисам, а именно включить программный фильтр трафика поступающего на сайт. Этот фильтр будет отсеивать трафик ботов участвующих в атаке, при этом пропуская легитимный трафик “живых” посетителей сайта.
Схема программного фильтра от DDoS атак на сайт
Фильтр основан на том, что боты участвующие в DDoS атаках не способны выполнить JavaScript код, соответственно боты не пройдут дальше стоп-страницы фильтра, чем существенно разгрузят фронтенд/бекэнд и базу данных сайта. Т.к. на обработку каждого GET/POST запроса DDoS атаки потребуется выполнить не более 20 строк кода в бэкенде сайта и выдать страницу-заглушку объемом менее 2Кб данных.
- Фильтр вызываем первой строкой веб-приложения, до вызова всего остального кода приложения. Так можно максимально разгрузить “железо” сервера и уменьшить объем отдаваемого трафика в сторону ботов.
- Если посетитель попадает под условия фильтра, то выдаем посетителю специальную страницу-заглушку. На странице,
- Сообщаем о причинах выдачи специальной страницы вместо запрошенной
- Устанавливаем специальную куку в броузере пользователя посредством JavaScript.
- Выполняем JavaScript код перенаправления на исходную страницу.
- Если у посетителя установлена специальная кука, то фильтр прозрачно пропускает посетителя на запрошенную страницу сайта.
- Если IP адрес посетителя принадлежит автономной системе из списка исключений, то трафик так же прозрачно пропускаем. Это условие необходимо для исключения фильтрации ботов поисковых систем.
Проект фильтра на github.com.
Синтетические тесты фильтра
Тестировали утилитой ab от Apache Foundation на главной странице боевого сайта, предварительно сняв нагрузку с одной из нод.
Результаты с отключенным фильтром,
ab -c 100 -n 1000 https://cleantalk.org/
Total transferred: 27615000 bytes
HTML transferred: 27148000 bytes
Requests per second: 40.75 [#/sec] (mean)
Time per request: 2454.211 [ms] (mean)
Time per request: 24.542 [ms] (mean, across all concurrent requests)
Transfer rate: 1098.84 [Kbytes/sec] received
Теперь тоже самое с включенным фильтром,
Total transferred: 2921000 bytes
HTML transferred: 2783000 bytes
Requests per second: 294.70 [#/sec] (mean)
Time per request: 339.332 [ms] (mean)
Time per request: 3.393 [ms] (mean, across all concurrent requests)
Transfer rate: 840.63 [Kbytes/sec] received
Как видно из результатов тестирования, включение фильтра позволяет обработать веб-серверу почти на порядок больше запросов чем без фильтра. Естественно речь идет только о запросах, от посетителей без поддержки JavaScript.
Применение фильтра на практике, история спасения сайта от одной небольшой DDoS атаки
Периодически мы сталкиваемся с DDoS атаками на наш собственный, корпоративный сайт https://cleantalk.org. Собственно во время крайней из атак мы и применили фильтр от DDoS на уровне веб-приложений сайта.
Начало атаки
Атака началась в 18:10 UTC+5 18 Января 2018 года, атаковали GET запросами на URL https://cleantalk.org/blacklists. На сетевых интерфейсах Front-end серверов появились дополнительные 1000-1200 кбит/секунду входящего трафика, т.е. получили нагрузку 150/секунду GET запросов к каждому серверу, что выше штатной нагрузки в 5 раз. Как следствие резко вырос Load average серверов Front-end и серверов баз данных. В результате сайт начал выдавать ошибку 502 по причине отсутствия свободных процессов php-fpm.
Анализ атаки
Потратив некоторое время на изучение логов, стало понятно что это именно DDoS атака, т.к.,
- 5/6 запросов приходились на один и тот же URL.
- Отсутствовала ярко выраженная группа IP адресов создающая нагрузку на URL из пункта 1.
- CPU фронтенд серверов был загружен на порядок выше, чем всплеск нагрузки на сетевых интерфейсах.
Соответственно было решено включить фильтр посетителей сайта по алгоритму описанному выше, дополнительно включив в него проверку входящего трафика по нашей БД черных списков, тем самым уменьшив вероятность выдачи стоп-страницы легитимным посетителям сайта.
Включение фильтра
Потратив еще некоторое время на подготовку фильтра, в 19:15-19:20 он был включен.
Спустя несколько минут получили первые положительные результаты, сначала Load average вернулся к норме, затем упала нагрузка на сетевых интерфейсах. Спустя несколько часов атака повторилась дважды, но ее последствия были практически незаметны, фронтенды отработали без ошибок 502.
Заключение
В итоге, применением простейшего кода JavaScript мы решил задачу фильтрации трафика от ботов, тем самым погасили DDoS атаку и вернули показатели доступности сайта к штатному состоянию.
Честно говоря, этот алгоритм фильтрации ботов был придуман не в день атаки описанной выше. Еще несколько лет назад реализовали дополнительную функцию SpamFireWall к нашему Антиспам сервису, SpamFireWall используют более чем 10 тысяч веб-сайтов и о нем есть отдельная статья.
SpamFireWall был разработан прежде всего для борьбы со спам-ботами, но так как списки спам-ботов пересекаются со списками прочих ботов, используемых для сомнительных целей, то применение SFW вполне эффективно в том числе и для купирования небольших DDoS атак на сайт.
О сервисе CleanTalk
CleanTalk это облачный сервис защиты веб-сайтов от спамботов. CleanTalk использует методы защиты, которые незаметны для посетителей веб-сайта. Это позволяет отказаться от методов защиты, которые требуют от пользователя доказать, что он человек (captcha, вопрос-ответ и др.).
Комментарии (21)
Ghost_nsk
27.04.2018 07:58+11. ab это очень синтетика, как минимум потому что без keep-alive, если нападающий будет держать соединение, бекэнд у вас очень быстро закончится
2. кука в JS ставится так топорно что с парсером справится даже начинающий, и нет больше вашей защиты
3. зачем пользователя задерживают на 3 секунды? только для того что бы он прочитал о вашей крутой системе?
4. в описанном вами случае достаточно было настроить кеширование на несколько минут на стороне nginx по любой статье из поисковика. На той странице нет персонализированных данных, данные обновляются относительно редко, нет смысла дергать бекэнд на каждый запрос.shagimuratov Автор
27.04.2018 09:491. В случаи небольших DDoS атак возможности веб-серверов по количеству одновременных соединений выше количества атакуемых.
2. Не спорю, этот вариант с JS кодом не сложен. Но тем не менее позволит выиграть время.
3. 1 секунду и менее показывать не правильно с точки зрения посетителя сайта, т.к. он не успеет понять почему он вдруг мельком увидел какую-то другую страницу вместо ожидаемой.
4. Одно другому не мешает.
ximaera
27.04.2018 09:57+2боты участвующие в DDoS атаках не способны выполнить JavaScript код
Ну, это вам повезло так. Не факт, что каждый раз будет везти.shagimuratov Автор
27.04.2018 10:31Выполнение JavaScript затратная и не дешевая операция. Есть реальные примеры таких атак? Не поделитесь кейсами?
Был опыт преднамеренного усложнения JavaScript кода в целях исчерпать ресурсы атакующих?ximaera
27.04.2018 11:03В современном браузере поверх Javascript можно Windows 98 запустить. Так что тезис про затратность — он, конечно, в некотором смысле справедлив, но всё относительно.
Идея в том, что исполнитель DDoS-атаки за потребляемые ботами ресурсы не платит, так как бот — это просто malware на компьютере какого-нибудь незадачливого пакистанского — или же рязанского — гражданина. Поэтому переживать насчёт пары JS-редиректов никто не будет. Даже вот Хабр и тот больше JS выполняет, как я посмотрю, не говоря уже о Facebook и Gmail (которые вообще обычно у людей просто резидентно в фоновых вкладках работают, и никто на этот счёт не переживает).
hololoev
27.04.2018 10:27Уже очень давно есть testcookie-nginx-module github.com/kyprizel/testcookie-nginx-module и статья на хабре: habr.com/post/139931
Умеет на много больше, написан на Си, под Ваши нужды- идеально.shagimuratov Автор
27.04.2018 10:29Не у всех Nginx веб-сервером и не все имеют доступ к его настройкам (shared hosting).
Решение с 1 include в index.php проше и универсальнее.jodaka
27.04.2018 12:32Выглядит, скорее, как костыль, чем как решение :-\
Но мне вообще сложно представить себе сколь либо сорьёзный сайт, который пытается защититься от DDOS сидя на shared хостинге и не будучи прикрытым nginx — видимо я мало знаю жизнь :)
apapacy
27.04.2018 15:23Решение в принципе просто и лучше чем ничего. Могу указать на сопутсвующие проблемы.
Защита должна отсеять ботов но пропустить нормальных клиентов.
Ваше решение будет отсеивать все запросы Ajax и не GET после смены клиентом IP-адреса. Это довольно частое явление, т.к. клиент может перейти их одной сети Wi-Fi в другую сеть или же перейти на мобильный интернет и вернуться отять в Wi-Fi. При некотроых настройках эта ошибка появляетя и в том модуле на С++ на который есть ссылка выше в комментариях. Приходилось снимать IP из анализа чтобы ее избежать. Но это ослабляет защиту.
Кстати показывать страницу с текстом совсем не обязательно. Можно закрузить пустую страницу со скриптом в котором вызвать перегрузку через скажам 0,2 с.
Также не защищаются статические ресурсы которые могут породить большой трафик (например большие картинки) и все равно запускается скрипт на PHP что конечно не так накладно как если бы загружался полный фреймерк и запрашивалать база данных, но все равно отнимает лишние ресурсы и снижает защищенность от их исчерпания.
Наиболее оптимально делать то же примерно самое но на скриптовом движке Lua, который уже встроен в nginx и в собранном виде доступен при инсталляции nginx-extras или же openresty.shagimuratov Автор
27.04.2018 16:32Смена IP адреса приведет к повторной выдачи стоп-страницы, что не приятно, но не критично для посетителя.
Пустая страница не информативна, будет не понятна пользователям, а следовательно повлияет на сценарий работы с сайтом, в плоть до обращения в тех. поддержку.
Статичные ресурсы отдаются веб-сервером, а его производительность на порядки выше чем PHP бекенда. Т.е. в условия DDoS атаки менее 1к пакетов в секунду, это не будет проблемой.
Опять же Nginx хорошо, но не все его используют, либо не все имеют доступ к его настройкам.apapacy
27.04.2018 17:30Это не совсем так. Код API на php могут вызывать по Ajax которые не пергружают страницу. И прегрузка reload() страницы по запросу POST приведет к потере тела запроса. Т.к. reload() делает запрос GET.
Я не спорю что такое решение как у Вас лучше чем ничего. Но по опыту работы с клиентами знаю что когда Ваша зщита отразила атку никто не будет входить в подробности о том что сервис отразил атаку. Это остается за кадром. Но начинаются вопросы о том что наш клиент написал в поддрежку что сайт не работает, сделайте наконец что нибудь.shagimuratov Автор
28.04.2018 09:58У реальных посетителей, стоп-страница будет 1 раз за посещение сайта, время показа можно варьировать по необходимости.
Кроме того, стоп-страницу можно использовать для информирования клиентов о произошедшем, и о том что делать, и что не делать.apapacy
28.04.2018 13:10В реальной жизни страница будет всякий раз при смнен адреса. это может происходить несколько раз в течение одного помещения сайта. А если сайт построен как spa то он просто перестанет рпботать
redfs
Навскидку глядя на схему — что мешает получить куку ct_anti_ddos_key, а потом атаковать сайт, подставляя ее в запросы?
shagimuratov Автор
Ничего не мешает, но это усложняет техническую реализацию атаки, т.к. есть привязка куки к IP адресу атакующего.
Приведенную выше схему можно рассматривать как один из элементов защиты от DDoS, если включить в начале атаки, то выиграете время на усиления анти DDoS защиты.
Ghost_nsk
привязка к IP адресу очень упрощает техническую реализацию. У вас для запросов с одного IP отдается одна и та же кука!
Вы поди за отражение атак ещё и деньги берете?
shagimuratov Автор
Коллега, все что сложнее потребует поддержку систем хранения данных, а это уже будет не универсально и гораздо менее производительно чем сейчас.
И никто не мешает усложнить JavaScript код под конкретный сайт.
Ghost_nsk
добавьте хотя бы TTL в метку, что бы она жила не более пары минут
shagimuratov Автор
TTL для tcp? Если так, то не вижу способов влиять на TCP пакеты из PHP/JavaScript кода.
Ghost_nsk
Время жизни ключа. Не время жизни куки. Кроме IP добавляете к ключу время создания, в PHP при проверке определяете время создания ключа, если с момента его создания прошло некоторое время — считаете его не действительным.
shagimuratov Автор
Теперь идея понятна, в принципе реализуема там же в куках! Возьму на заметку.