Как защититься от dos/ddos, или Как я начал вновь высыпаться по ночам +10


Предисловие

Приветствую тебя, дорогой читатель. Скорее всего, если ты это читаешь, то уже очень устал получать кучу алертов о том, что твоя площадка загибается от регулярных набегов печенегов ботов или других нежелательных посетителей контента. Я надеюсь, что эта статья поможет тебе спать спокойно и оградит тебя от недугов, а также внесет больше ясности в твое понимание теории и практики защиты от dos и ddos. Приятного чтения!


Глава 1: “Знай своего врага.” - Сунь Цзы

Что такое Dos и Ddos, и в чём их отличия?

Dos, или по-другому Denial of Service - «отказ в обслуживании». Это атака со стороны одиночного IP-адреса, связанная с отправкой нелегитимного трафика и приводящая к неработоспособности системы, что, в свою очередь, приводит к простою площадки или сервиса и потере финансовой составляющей.

Ddos, или Distributed Denial of Service - «распределенный отказ в обслуживании».  Отличие данной атаки от описанной выше в том, что отправка трафика в данном случае будет идти с различных IP-адресов. Этот аспект приводит к сложности защиты сервисов.

Как распознать возникновение атаки на инфраструктуру?

В случае, если на вашей машине, локальной или виртуальной, резко повысилась нагрузка на CPU и увеличилось потребление RAM, то, вероятнее всего, вы стали жертвой криво залитого кода или же ddos/dos-атаки. Распознать подобное поведение и определиться с окончательным выбором варианта ответа нам помогут следующие шаги.

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

Также вы заметите увеличение количества коннектов на вашем сервисе обработчике данных. Для примера мы рассматриваем стандартный LEMP-стек Nginx + Apache + Mysql,  а значит в момент проблемы мы фиксируем резкий скачок соединений:

Во-вторых можно произвести анализ запросов за время возникновения проблемы. В этом, как правило, могут помочь однострочные скрипты, которые полезно иметь каждому системному администратору под рукой, данные скрипты вы можете написать самостоятельно, изучив команды чуть подробнее или воспользоваться нашим готовым решением. Ниже команда для парсинга логов за последние 10000 и вывод в top IP адресов, встречающихся наиболее часто:

tail -10000 access.log | awk '{print $1}'| sort -nr | uniq -c | sort -nr | head

Так можно узнать количество уникальных IP, после чего можно сравнить с результатами за предыдущие сутки и узнать было ли значительное увеличение трафика:

cat access.log | awk '{print $1}'| sort -nr | uniq -c | wc -l

Следом представляем отличную команду для парсинга IP-адресов в момент фиксирования проблемы, а именно по дате и временному промежутку:

head access.log | grep 26/Apr/2021:1[1-2]: | awk '{print $1}'| sort -nr | uniq -c | sort -nr

Теперь давайте посмотрим примеры на практике нелегитимного трафика:

head -20 access.log | grep "25/Jul/2022:00:" | awk '{print $1}' | sort | uniq -c | sort -nr
   4150 45.155.53.76
   3550 84.38.189.56
   3184 5.45.207.189
   233 5.255.253.188
   233 45.10.217.121
   233 92.119.203.156
   229 5.45.207.112
   223 84.38.189.54
   200 109.194.11.25
   147 5.255.253.160

В этом примере первое, что бросается в глаза - это около 4000 запросов за последний час с 00:00. Из этого можно сделать вывод, что это явно не обычный пользователь, которому нравится заходить на сайт. Также одним из отличительных признаков ddos-атаки можно назвать регулярное изменение user agent в каждом запросе, что наводит на мысль о машинном создании запроса.

Что же такое user agent?

User agent — идентификационная строка клиентского приложения; обычно используется для приложений, осуществляющих доступ к веб-сайтам — браузеров, поисковых роботов и «пауков», мобильных телефонов и других устройств со встроенным доступом к веб-ресурсам.

Например:

tail -10 access.log | grep 185.250.45.31
185.250.45.31 [Via:NGENIX; X-Proxy-Name:-] (RU) "www.testsite.ru" [24/Jul/2021:21:27:24 +0300]    200     "GET /ajax/rq_list.php?product_id=10692404 HTTP/1.1"    0.049 (0.048)   Upstream:10.250.0.5:443 1963    "https://www.testsite.ru/ajax/rq_list.php?product_id=10692404" "Mozilla/5.5 (Windows; I; Windows NT 6.1; en-us) Chrome/36.6.59.965"    "blocked=0"     "whitelistCOUNTRY=1"    "whitelistIP=0" "whitelistORG=0" 
185.250.45.31 [Via:NGENIX; X-Proxy-Name:-] (RU) "www.testsite.ru" [24/Jul/2021:21:27:24 +0300]    200     "GET /ajax/rq_list.php?product_id=10692404 HTTP/1.1"    0.056 (0.056)   Upstream:10.250.0.5:443 1963    "https://www.testsite.ru/ajax/rq_list.php?product_id=10692404" "Mozilla/5.3 (compatible; Windows NT 6.6; WOW64; en-us) Chrome/35.1.309.843 Safari/551.56"      "blocked=0"     "whitelistCOUNTRY=1"    "whitelistIP=0" "whitelistORG=0" 
185.250.45.31 [Via:NGENIX; X-Proxy-Name:-] (RU) "www.testsite.ru" [24/Jul/2021:21:27:27 +0300]    200     "GET /ajax/rq_list.php?product_id=10692404 HTTP/1.1"    0.063 (0.064)   Upstream:10.250.0.5:443 1963    "https://www.testsite.ru/ajax/rq_list.php?product_id=10692404" "Mozilla/5.9 (Linux (Wine); Windows NT 7.2; ru-ru) like Gecko Chrome/34.8.208.96 Safari/639.27" "blocked=0"     "whitelistCOUNTRY=1"    "whitelistIP=0" "whitelistORG=0" 
185.250.45.31 [Via:NGENIX; X-Proxy-Name:-] (RU) "www.testsite.ru" [24/Jul/2021:21:27:27 +0300]    200     "GET /ajax/rq_list.php?product_id=10692404 HTTP/1.1"    0.071 (0.072)   Upstream:10.250.0.5:443 1963    "https://www.testsite.ru/ajax/rq_list.php?product_id=10692404" "Mozilla/5.0 (compatible; U; Windows NT 6.3; en-us) Chrome/35.1.226.692"        "blocked=0"     "whitelistCOUNTRY=1"    "whitelistIP=0" "whitelistORG=0" 
185.250.45.31 [Via:NGENIX; X-Proxy-Name:-] (RU) "www.testsite.ru" [24/Jul/2021:21:27:28 +0300]    200     "GET /ajax/rq_list.php?product_id=10692404 HTTP/1.1"    0.073 (0.072)   Upstream:10.250.0.5:443 1963    "https://www.testsite.ru/ajax/rq_list.php?product_id=10692404" "Mozilla/5.0 (Windows; Windows NT 6.9; x64; en-us) KHTML/4.3.5 (like Gecko) Chrome/39.3.54.483" "blocked=0"     "whitelistCOUNTRY=1"    "whitelistIP=0" "whitelistORG=0" 

Здесь прекрасно видно, как на протяжении 1 минуты useragent потенциального пользователя в каждом запросе меняет браузер на Chrome, Mozilla, Safari. Как и саму ОС, с которой происходит отправка Windows, Linux.

Итак, когда мы чуть-чуть познакомились с теорией, настало время практики. В частности, мы поднимем стандартный Nginx на OC Debian 10 в качестве обработчика статического контента и Apache в качестве backend и произведем стресс тест нашей площадки.

Глава 2: “Лучшая защита - нападение” - Александр Македонский.

Начнём… Давайте поставим актуальный Nginx из официальных репозиториев для дальнейшей работы.

1.1. Ставим необходимые библиотеки:

apt install curl gnupg2 ca-certificates lsb-release debian-archive-keyring

1.2. Импортируем официальный ключ подписи:

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
    | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

1.3. Проверяем его правильность:

gpg --dry-run --quiet --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg

1.4. Добавляем репозитории:

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/debian `lsb_release -cs` nginx" \
    | tee /etc/apt/sources.list.d/nginx.list

1.5. Настраиваем приоритет наших пакетов, над пакетами из основного дистрибутива:

echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
    | tee /etc/apt/preferences.d/99nginx

1.6. Перечитываем репозитории и ставим наш Nginx

apt update
apt install -y nginx

2. Проверяем, что Nginx установлен корректно и уже автоматически запустился.

systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
  Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
  Active: active (running) since Mon 2022-01-31 18:19:12 +07; 7s ago
    Docs: man:nginx(8)
Main PID: 9381 (nginx)
   Tasks: 5 (limit: 4915)
  Memory: 5.8M
  CGroup: /system.slice/nginx.service
          ├─9381 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
          ├─9382 nginx: worker process
          ├─9383 nginx: worker process
          ├─9384 nginx: worker process
          └─9385 nginx: worker process

янв 31 18:19:12 user systemd[1]: Starting A high performance web server and a reverse proxy server...
янв 31 18:19:12 user systemd[1]: Started A high performance web server and a reverse proxy server.

3. Теперь напишем простую конфигурацию для виртуального хоста и проведём тестирование.

nano /etc/nginx/conf.d/default

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

server {
       listen 80 ;

       root /var/www/html;

       server_name www.zashitimsa_ot_ddos.ru;

       location / {
               try_files $uri $uri/ =404;
       }

}

4. Прописываем необходимую запись к себе в hosts, чтобы иметь возможность открыть наш созданный сайт.

nano /etc/hosts

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

$наш_ip_адрес www.zashitimsa_ot_ddos.ru

5. Делаем проверку и получаем подобный результат.

p.s. Картинка

Картинка была заготовлена изначально и положена в файлы площадки.

6. Теперь проверим, как наш Nginx выдержит стресс-тест. Используем для этого любую тестовую утилиту для Dos. Я в качестве примера использую GoldenEye. Ссылка на источник прилагается.

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

Перемещаемся в рабочий каталог:

cd /opt

И клонируем себе репозиторий:

git clone https://github.com/jseidl/GoldenEye.git
cd ./GoldenEye

Если у вас изначально нету git и python, то их можно также легко установить себе на машину следующими командами:

apt install git python3.9

Теперь делаем тестовый запуск и смотрим на логи и результат:

python3 goldeneye.py http://www.zashitimsa_ot_ddos.ru/test.png -w 50 -s 1500 -m random -d
GoldenEye v2.1 by Jan Seidl <jseidl@wroot.org>

Hitting webserver in mode 'get' with 2500 workers running 500 connections each. Hit CTRL+C to cancel.

Starting 50 concurrent workers

7. Результат.

Площадка не отдает контент:

В логах видим, что на площадку было отправлено 3000 беспорядочных запросов Nginx.

head /var/log/nginx/access.log | grep 07/Feb/2022:05: | awk '{print $1}'| sort -nr | uniq -c | sort -nr
   3002 62.109.28.95

При этом все запросы имеют примерно следующее содержание:

tail -3 access.log 
62.109.28.95 - - [07/Feb/2022:05:50:40 +0000] "GET /test.png?C5fpRAtsX=mDtamkj&fvHhGpqK5E=wFsRJifnxpAr8yXYA8Ip HTTP/1.1" 200 119941 "http://www.zashitimsa_ot_ddos.ru/mCiN2DNf7Y?N1tw=c7aqmB&0lFIpn=pUYexNiiySDc2&fw1h=3KiPFpk30NerKd6&2F1bOYVUhl=JoFsDn2&fg7ljW=b7KNWm&qE2=QtSctM2c53dVRUlx&eaQ=aTP18q14oKF" "Mozilla/5.0 (compatible; MSIE 7.0b; Windows NT.6.2; .NET CLR 2.5.24981; WOW64)"
62.109.28.95 - - [07/Feb/2022:05:50:40 +0000] "GET /test.png?lTbPcEcrHp=24ohNGEqR0 HTTP/1.1" 200 138765 "-" "Mozilla/5.0 (compatible; MSIE 7.0; Macintosh; .NET CLR 3.0.6224; Intel Mac OS X 10_2_5)"
62.109.28.95 - - [07/Feb/2022:05:50:40 +0000] "GET /test.png?jUqMn0gKkI=w2EUD4Ycvt&dSYvge=x0W&vXBaoy7=pvpl21LV&1tssU73vTd=eoqNCWqcv60&BG1FRwnEV2=8qnklbsrP HTTP/1.1" 200 121389 "-" "Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.35 (KHTML, like Gecko) Chrome/27.0.229.36 Safari/536.2"

Это привело к исчерпанию свободных workers обработчиков сервиса:

tail -5 /var/log/nginx/error.log
2022/02/07 05:50:30 [alert] 29231#29231: 768 worker_connections are not enough
2022/02/07 05:50:30 [alert] 29231#29231: 768 worker_connections are not enough
2022/02/07 05:50:30 [alert] 29231#29231: 768 worker_connections are not enough
2022/02/07 05:50:30 [alert] 29231#29231: 768 worker_connections are not enough
2022/02/07 05:50:30 [alert] 29231#29231: 768 worker_connections are not enough
p.s. Предосторожность!

Будьте аккуратны с GoldenEye, так как во время тестирования он потребляет основные ресурсы сервера и, как правило, при неправильном выставлении параметров из серии:

python3 goldeneye.py http://www.zashitimsa_ot_ddos.ru/test.png -w 5000000 -s 15000000 -m random -d

Может привести к исчерпанию свободных ресурсов сервера и к его зависанию.

Глава 3: “В сражении само по себе численное превосходство не дает преимущества. Не надо идти в атаку, опираясь только на голую военную мощь.” - Сунь Цзы

После того, как наша площадка спустя непродолжительное время упала под натиском атаки, можно пробовать выстраивать защиту, которая сможет помочь избежать нелегитимного трафика и дать возможность почувствовать себя крутым IT-шником.

Перед этим необходимо ответить себе на следующие вопросы:

  1. От каких атак я хочу защититься?

  2. Хочу ли я отсеять запросы ботов?

  3. Как не потерять трафик от реальных пользователей?

Приступим...
Разумеется, для тех, кто уже имел опыт работы с Nginx, не будет сюрпризом функция ‘rate’. Для тех, кто не имел такого опыта, ниже мы подробнее рассмотрим эту функцию: что это и с чем это едят.

Шаг первый ‘rate’:

rate - настраиваемая функция, которая позволяет вам ограничить количество HTTP-запросов, которые пользователь может сделать за определенный период времени.

1. Производим добавление зоны в блок server нашего виртуального хоста в корневой location:

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

limit_req_zone $binary_remote_addr zone=shield:10m rate=1r/s;

server {
       listen 80 ;

       root /var/www/html;

       server_name www.zashitimsa_ot_ddos.ru;

       location / {
              limit_req zone=shield;
               try_files $uri $uri/ =404;
       }

}

Чтобы понимать, что мы пишем, разберём сразу несколько ‘лемм’ данной настройки:

zone   /// Ни для кого не будет секретом, что зона представляет из себя зону общей памяти, используемую для хранение IP адресов
zone=shield  /// Название зоны, оно может быть любым
$binary_remote_addr  /// переменная содержащая в себе двоичный IP адрес
10m  /// размер зоны памяти в мегабайтах                                                                                
rate=1r/s /// количество requests в секунду, в данном случае 1 запрос в секунду

Результаты:

После произведения небольшого теста мы сразу увидим в логах следующую картину работы нашей настройки:

head access.log
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?gtv0SN=QwAgT&6SAGxK3h=AFw0opHJed3XpCtwHb HTTP/1.1" 200 189721 "-" "Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/535.20 (KHTML, like Gecko) Version/5.0.4 Safari/536.21"
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?c6aJYc=lwveJ5aI76xw&ogFuK8R4W=CUqFlTXSrhT1S&F3v=R7EhYI7gpucmbq4&xqN=cq3coSVvPT6&HHq=vjnt7FFyKLx HTTP/1.1" 503 213 "-" "Mozilla/5.0 (Windows NT 6.3; WOW64) Gecko/20042612 Firefox/19.0"
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?2L6Ymgh=mvoXScoSmiaM&mjjkt3o=Lkwyw&5I1ym=7VKefXOma HTTP/1.1" 503 615 "http://www.google.com/xNlsQ3?1nvky0BRyy=JmTICugXrcPMXx0DC" "Mozilla/5.0 (compatible; MSIE 9.0; Macintosh; .NET CLR 2.2.26969; Intel Mac OS X 11_1_1)"
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?Xk4y=xsyeB7FObY3J&TQoJ5=VLTnHP HTTP/1.1" 503 615 "http://www.zashitimsa_ot_ddos.ru/TRvKqc" "Mozilla/5.0 (Windows; U; MSIE 7.0; Macintosh; .NET CLR 3.5.13570; Intel Mac OS X 11_0_5)"
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?A0wPrP=xcKcOdMTSFEGqaUw1&C4lwH=YcQgUkhR1QOI&NCeuc2=LDW5odrQN2bNcWhn2 HTTP/1.1" 503 213 "http://www.zashitimsa_ot_ddos.ru/IdMNN4?CKf7Ux=Jv1CuJ&kah4l8EofJ=o0BPFDqnOE5XWR&iJvEuVR=LEUud0hfi2m&0fRWX0=Fx6SkHP5VRIuJKt&xNQKBnLm=GqquF1C7PEWiKiHD8jG&thesU=HG80V0N&bWtoxEG=CpK8ju5E6lmYm7rOwsyC&wPt=uVQSsyk7XrW7Eq1h" "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) Gecko/20103004 Firefox/15.0"
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?5O0=3DmuAKsJUVPpdU&TkKL=wJJpKSqOo HTTP/1.1" 503 615 "http://www.yandex.com/ehfnY5" "Mozilla/5.0 (Windows NT.6.2; WOW64) AppleWebKit/537.19 (KHTML, like Gecko) Chrome/17.0.1671.100 Safari/536.22"
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?5ra=7qBQLV&h3YaO=JNUwUUpq HTTP/1.1" 503 213 "-" "Mozilla/5.0 (Windows NT 5.1; Win64; x64) Gecko/20072512 Firefox/11.0"
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?NiU=O3lRfkyh4cDRKo&KWEgQScD0=A4uYEw5rCUI55yt1&C5qjWevh=BJp&qY7q77=XlkE0 HTTP/1.1" 503 615 "http://www.google.com/gwqPjY?SnC=QjCvRl6WkihbOXx3VD&YBbXQ=kfK&u8y6r=qBMhgA67RbfqNOG&CH1jrHn=nJEpNgIdhXq6eP6AL6Y&6u2CM=mjry&KMge75d=4VjNynX&PVU=vWLeLCMRQ1O&ayQVLV=ynE0qdxKmwyQUR6X&uTwvDJU=gB86jE1bJh6PTp&31xQy=NYqINsYwjx8gx8" "Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 6.1; Trident/4.0; WOW64)"
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?wCoex=PrlSPlMIAkWGd3J&e7xBA6=ltFAGNQlQHCeGyh24&EWMvYuRt5=57lPG26Mq HTTP/1.1" 503 213 "http://www.yandex.com/loiWFn8?EcOgc=N44Hh1NgNpsTAF&H3dkdP5sps=MW6wACSn&YU4xDEL22u=3QnCuKkybO&MvpI4=RSHViVAvsScoE&d7bQuVeMms=FcGUbmWv&bR4P63j=TolMsDcX0GmrXHUQB&E8A8jR2wf=jl2BA4ipBva&xKgBJB=xnqJAic1ePkX81UfI" "Mozilla/5.0 (Linux i386; X11) Gecko/20100505 Firefox/25.0"
62.109.28.95 - - [07/Feb/2022:06:08:08 +0000] "GET /test.png?GBSOIJ=IVlXF&F7EoI30=bcRHF6ej2vX4i2vj&TE7=mWfJ2E47oe37cgjegjL&x4QKBtRDW=USvUq HTTP/1.1" 503 213 "http://www.baidu.com/GLbGL6q" "Mozilla/5.0 (Linux x86_64; X11) Gecko/20072512 Firefox/11.0"

Отсюда мы видим, 1 запрос получил 200-й код ответа, после чего все остальные запросы получали 503-й код, говорящий о том, что сервер не готов обработать данный запрос. Прекрасно - мы на верном пути.

После того как мы настроили данную защиту, мы защитились от вредоносных IP-адресов, которые создают ненужные запросы, но что, если мы представим ситуацию, когда к нам заглянут толпы поисковых ботов не принадлежащие Yandex, Google, Mail, исходя из нашей настройки 1 запрос будет всегда обрабатываться корректно, но нужен ли он нам?

Шаг второй ‘блокировка по user_agent’

Производим настройку блокировки по user_agent, добавим в наш блок сервера интересное условие для блокировки наиболее популярных поисковых ботов и получим финальную конфигурацию:

limit_req_zone $binary_remote_addr zone=shield:10m rate=1r/s

server {
       listen 80 ;

       root /var/www/html;

       server_name www.zashitimsa_ot_ddos.ru;

       
      if ( $http_user_agent ~* (bingbot|AhrefsBot|Ruby|PetalBot|SemrushBot|MJ12bot) ){
               return 403;
       }

       location / {
              limit_req zone=shield;
               try_files $uri $uri/ =404;
       }

}

Результаты:

Теперь боты, обращающиеся с данным user agent автоматически получают 403 независимо от запроса и отправляются отдыхать, не затрагивая нашу площадку.

62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?OBDmsO6=foc&kbCf=xy6VgK4QYN HTTP/1.1" 403 571 "-" "Mozilla/5.0 (MJ12bot; Intel Mac OS X 10_0_1) AppleWebKit/537.18 (KHTML, like Gecko) Chrome/30.0.1475.18 Safari/535.5"
62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?TteMB=tmrbqmDJabtTAT&cGr4=p2Yxpkdku&Afk20eI=wTibBYkQRqCwOxgh1HD&cULj3rci=POV2pj&rWtHK0=UwnWlr3el HTTP/1.1" 403 571 "-" "Mozilla/5.0 (Windows; U; MSIE 7.0; MJ12bot; .NET CLR 2.0.4936; Intel Mac OS X 11_1_1)"
62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?iAHhFb6HF=W8YtpeInTbX774IOHWM&VFU2BXL=Kgfe0n5d631 HTTP/1.1" 403 140 "-" "Mozilla/5.0 (MJ12bot; Intel Mac OS X 11_1_1) Gecko/20053010 Firefox/16.0"
62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?SH41W=bSxf8dkWfwK&W7yR=lqY0s0 HTTP/1.1" 403 169 "-" "Mozilla/5.0 (MJ12bot; Intel Mac OS X 11_3_3) Gecko/20091604 Firefox/10.0"
62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?P52AAb=gDsyT4foW&nwGCdNH=oMV HTTP/1.1" 403 571 "http://www.zashitimsa_ot_ddos.ru/hmcHaC?S7yLL=6S5wMd0gLqeU7pDVJhju&AIkWBRq7s=NRXF2LkivYBU85dCGa&Q3VhbAts=36R3Ybw&WagrK7x=eE4CAB5AbALM&3yY=GI8vgaCLa2BapE2&bkSLGi=2jVjFgDSdwtxj&EWWCesCSDc=P2qu&5djUfS=Yfin7q3t2UsPN8rbx" "Mozilla/5.0 (compatible; MSIE 7.0; MJ12bot; .NET CLR 3.0.27172; Intel Mac OS X 11_1_0)"
62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?grw=txjgo&XSrbd=mbcJAf2diPBOxh&mLl=OGunDrcoWkhP HTTP/1.1" 403 571 "-" "Mozilla/5.0 (MJ12bot; Intel Mac OS X 11_3_3) AppleWebKit/535.35 (KHTML, like Gecko) Chrome/29.0.1227.45 Safari/537.7"
62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?XmqsKdXD4p=JXm&Ehm=DofRoOaWIHukWIYHWbbo HTTP/1.1" 403 169 "-" "Mozilla/5.0 (MJ12bot; Intel Mac OS X 10_0_1) Gecko/20082911 Firefox/23.0"
62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?hcFy=3jiNPtYiL&G0ctkasjSP=qHebQoiF&FkPPo=0lrX0QyRq05tqmqBO2P5&iH1YlUT=1KkpEb4on0Ef HTTP/1.1" 403 140 "http://www.bing.com/7kLfQh" "Mozilla/5.0 (MJ12bot; Intel Mac OS X 11_3_3) Gecko/20091607 Firefox/16.0"
62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?hfiKSAITuR=qMl5GqfT6dim&AAdUx2=GKs HTTP/1.1" 403 199 "http://www.bing.com/FN2DCyTle" "Mozilla/5.0 (MJ12bot; Intel Mac OS X 11_7_5) AppleWebKit/535.27 (KHTML, like Gecko) Chrome/28.0.633.27 Safari/536.36"
62.109.28.95 - - [07/Feb/2022:06:27:41 +0000] "GET /test.png?EoJFcrq=DX53dVcC7U&7fx=Pwimtil50F5gYyR1d7 HTTP/1.1" 403 571 "http://www.bing.com/yPulwdB8S" "Mozilla/5.0 (compatible; MSIE 7.0b; MJ12bot; .NET CLR 3.0.27172; Intel Mac OS X 11_3_3)"

Шаг третий ‘fail2ban’

Пришло время для настройки отличного инструмента для защиты сервера и вашего продукта от вредоносного помешательства - fail2ban. Суть его работы такова, что он анализирует logs файлы запущенных программ и на основании выставленных условий производит блокировку IP-адреса на указанный промежуток времени.

1. Производим его установку на сервер

apt install fail2ban

2. Время настройки!

Копируем стандартный jail.conf и сохраняем его в jail.local:

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

После чего редактируем jail.local:

nano /etc/fail2ban/jail.local

И вставляем в конце jail.local. Конфигурация:

[nginx-limit-req]
enabled = true
filter  = nginx-limit-req
port    = http,https
logpath = /var/log/nginx/*error.log
findetime = 600
bantime = 600
maxretry = 5

Где:

enabled = true   /// включить фильтр
filter  = nginx-limit-req   /// название фильтра ‘nginx-limit-req’
port    = http,https   // название портов в данном случае 80 и 443
logpath = /var/log/nginx/*error.log   /// откуда парсить логи
findetime = 600   /// интервал в секундах, за которое событие должно повториться определённое количество раз
bantime = 600   /// время бана в секундах
maxretry = 5   /// количество повторений сообщения об ошибках
p.s. Не забываем!

Не забываем удалить повторяющиеся уже настройки данного фильтра в самой конфигурации иначе fail2ban не сможет перечитать конфиг и будет ругаться на ошибки.

3. Перезапускаем fail2ban и проверяем результат

fail2ban-client reload

Либо сделать restart сервиса:

systemctl restart fail2ban

Проверяем, что наш фильтр запущен:

fail2ban-client status nginx-limit-req
Status for the jail: nginx-limit-req
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /var/log/nginx/error.log
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

Результат:

В дальнейшем, когда запросы будут идти в Nginx, наш настроенный выше ‘rate’ будет писать сообщения об ошибках в /var/log/nginx/error.log.

Пример ответа из logs файлов:

2022/02/07 07:12:23 [error] 981#981: *203984 limiting requests, excess: 0.600 by zone "shield", client: 62.109.28.95, server: www.zashitimsa_ot_ddos.ru, request: "GET /test.png?ahcBAmSMG6=82a5yFHKiohqnB&AX4tIBX=msA50EPwJD7dB0eTabXe&RHxTyKgc=JBej4S5EGgeJDDA&TRGxNTpkq=AjtnEu&7rhhe=k6BW4UpFcmmNH8bC7N5 HTTP/1.1", host: "www.zashitimsa_ot_ddos.ru", referrer: "http://www.baidu.com/cxikKmt0?yonyA=TixYYDnOAG3XlORl&JdL4yV0iQ=7ep5APklt"

После этого в /var/log/fail2ban.log будет выведено сообщение о блокировке нашего подозрительного IP:

2022-02-07 07:14:22,208 fail2ban.actions        [3066]: NOTICE  [nginx-limit-req] Ban 62.109.28.95

и данный пользователь в дальнейшем не сможет отправлять запросы в течении 10 минут, так как будет заблокирован правилами iptables.

Шаг четвертый ‘geoip’

Если вы прекрасно знакомы со своей аудиторией и знаете из каких стран вы желаете видеть пользователей, то для решения этой задачи отлично подойдет модуль, способный производить отсеивание и блокировку нежелательного трафика по геолокации IP адреса.

Если ранее у нас уже был установлен Nginx, то для работы с geiop нам потребуется собрать модуль из исходников и подключить его.

1. Производим установку на сервер необходимых библиотек.

add-apt-repository ppa:maxmind/ppa
apt update
apt install libmaxminddb0 libmaxminddb-dev mmdb-bin

2. Компилируем модуль под нужную версию Nginx, у нас это будет 1.18.0

cd /root
wget http://nginx.org/download/nginx-1.18.0.tar.gz
git clone https://github.com/leev/ngx_http_geoip2_module.git
tar zxvf nginx-1.18.0.tar.gz
cd nginx-1.18.0
./configure --with-compat --add-dynamic-module=../ngx_http_geoip2_module
make modules

3. Кладём его в наш каталог

mkdir -p /etc/nginx/modules
cp -vi objs/ngx_http_geoip2_module.so /etc/nginx/modules/

4. Скачиваем актуальные БД с IP адресами:

mkdir /etc/nginx/geoip ; cd /etc/nginx/geoip ; wget https://git.io/GeoLite2-Country.mmdb

5. После чего редактируем nginx.conf:

nano /etc/nginx/nginx.conf

И вставляем в блок http следующую мапу. Конфигурация:

load_module modules/ngx_http_geoip2_module.so;

...

http { 

        geoip2 /etc/nginx/geoip/GeoLite2-Country.mmdb {                                                                                                                                                                                                                         
          $geoip2_data_country_iso_code country iso_code;                                                                                                                                                                                                                      
       }

        map $geoip2_data_country_iso_code $is_deny {                                                                                                                                                                                                                                                                                                                                                                                                                                                               
           default no; # Запрещаем всем
           RU yes; # Разрешаем России
	    UK yes; # Разрешаем Великобритании
       }


}

6. Добавляем код location в наш виртуальный хост, как это делалось ранее. Конфигурация:

limit_req_zone $binary_remote_addr zone=shield:10m rate=1r/s

server {
       listen 80 ;

       root /var/www/html;

       server_name www.zashitimsa_ot_ddos.ru;

       
      if ( $http_user_agent ~* (bingbot|AhrefsBot|Ruby|PetalBot|SemrushBot|MJ12bot) ){
               return 403;
       }

      if ($allowed_country = no) {
          return 403;
      }

       location / {
              limit_req zone=shield;
               try_files $uri $uri/ =404;
       }

}

7. Посылаем команду нашему любимому Nginx на перечитку конфигураций и проверяем результат.

nginx -s reload

Если вы хотите узнать более подробную информацию про сборку динамических модулей, то вы можете ознакомиться со статьёй написанной ранее нашим DevOps Engineer.


Итог: “У каждого поколения должна быть своя война.” - Мао Цзэдун

Нельзя не заметить, что данная статья повествует о различных способах защиты от dos, но остаётся главный вопрос, который, я уверен, за время чтения уже был задан не однократно: “Как защититься от ddos?”. Ведь высококачественная атака, использующая большое количество IP-адресов может с лёгкостью обойти все уровни защиты, выставленные нами ранее. В данном случае лучше всего помогают следующие средства, а именно платформы по предоставлению услуг защиты и спокойного сна: Cloudflare, Incapsula, Servicepipe,  DDoS-Guard и etc

И хотя готовых решений может быть бесчисленное множество, необходимо уже изначально, на раннем этапе создавать грамотную настройку основного функционала сервиса обработчика. Ведь это позволяет с самого начала дать отпор ботам и недоброжелательным конкурентам, которые хотят создать проблемы в работе вашей инфраструктуры.

Берегите свои площадки и не допускайте простоя! 

P.S.: Кстати, DevOps-инженерам и системным администраторам будет интересно заглянуть в телеграм-канал DevOps FM, присоединяйтесь)




Комментарии (19):

  1. andreymal
    /#24339970 / +2

    4000 запросов за час? Это и близко не ddos, я столько от обычных клиентов получаю иногда

    Bingbot'а за что забанили?

    Скачиваем актуальные БД с IP адресами:

    По указанной ссылке скачивается база MaxMind, а он запретил использовать свои базы в России с 25 апреля

    • berzerk54
      /#24340040

      Ну так-то и PetalBot тоже является поисковым ботом huawei, но как показала практика, частенько с этих юзерагентов валится какое-то подозрительно высокое количество запросов..

      • andreymal
        /#24340106

        Ну и в любом случае все упомянутые боты вроде соблюдают правила из robots.txt, так что достаточно было бы просто туда соответствующие User-agent и Disallow прописать да и всё

    • Ну, это с одного IP адреса, но да, как-то мало. Скорее всего сами и устроили, чтобы в статью графики вставить. Ну ок, что уж тут такого). Важно ещё учитывать, что за сайт. Я не удивлюсь, если окажется, что за 5 мин, которые я таскаю, правлю и т. д. задачки по Jira я накручиваю примерно такое кол-во запросов (вот купит Маск джиру...).

      А вообще да, там вон Azure уже в 2021 году отразил атаку 340 000 000 RPS. Длился праздник не долго - у хакеров тоже, знаете, не бесконечность денег, но вот это можно назвать серьёзной DDoS атакой.

    • Gacblk
      /#24340966 / -2

      Так там и нет утверждений, что это ddos . Фраза "одним из отличительных признаков" я думаю прекрасно говорит об этом. Если у вас достаточно большое количество легитимного трафика поступает от пользователей и вы прекрасно знаете об этом, я думаю логика сравнительного анализа информации из логов на основании предыдущего интервала времени поможет вам в любом случае вычленить вредоносный IP, делать подобные выводы можно лишь на основе совокупности факторов, которые также прекрасно описаны выше.

      4000 запросов за час? Это и близко не ddos, я столько от обычных клиентов получаю иногда

      Прежде чем утверждать какую-либо информацию необходимо проверить со своей стороны можете ли вы осуществить скачивание базы по указанной ссылке. При проверке на текущий момент я не фиксирую каких-либо проблем. Но даже если они у вас и есть, я думаю вы можете прекрасно ознакомиться со статьями про VPN, которых за последнее время появилось не мало.

      По указанной ссылке скачивается база MaxMind, а он запретил использовать свои базы в России с 25 апреля

      • andreymal
        /#24341002 / +2

        можете ли вы осуществить скачивание базы по указанной ссылке

        Если могу технически - это ещё не значит, что могу по закону. Успешно скачанная в Россию база по указанной ссылке автоматически нарушает лицензионное соглашение MaxMind (и отсутствие текста этого соглашения, приложенного к ссылке, - тоже нарушение)

        • andreymal
          /#24341088

          Немного проясню: в текущем тексте соглашения Россия не упоминается (забыли добавить?), но в апреле MaxMind разослал российским аккаунтам письма счастья о том, что они присоединяются к санкциям и отбирают доступ, руководствуясь пунктом 10 этого соглашения


          Там есть и другие интересные пункты, например пункт 6c требует регулярно скачивать обновления, удалять старые версии базы да ещё и письменно уведомлять MaxMind о проведённом удалении по их запросу


          Возможно, вместо MaxMind стоит попробовать какой-нибудь DB-IP Lite, у которого лицензия — чистый Creative Commons без дополнительных условий (но у меня попробовать пока руки не дошли)

  2. Expany
    /#24340136 / +2

    Из всего описанного, упоминания достойно только использование GeoIP в виду последних событий, и разумеется fail2ban, крайне вскользь упомянут iptables (у него вроде есть более современный аналог, применяемый ныне), про который стоило сказать гораздо больше.

    Использование rate - вредная опция, так как в случае, если на 1 страницу приходится статики типа img, js, css помимо html больше чем указан лимит, ресурсы не будут получены. Как результат, реальный пользователь получит криво работающий сайт. Нужно либо постоянно держать в голове число из лимита, либо намеренно указывать его с запасом.

    • ColdPhoenix
      /#24341464 / +2

      статику вообще надо из rate исключать(или другой лимит ставить)

  3. DarkHost
    /#24340152 / +4

    Какая-то детская защита от ддос для локалхоста.

    1. Если это не сайт-открытка, а, например, сайт с апи, то блокировка по гео просто парализует работу. Сейчас многие в облаках, и гео будет скорее вредной, если клиент в каком-нибудь AWS. Если сайт и апи разделены, то наличие гео блока для сайта еще норм.

    2. Для нормального сайта, с хорошей посещаемостью, даже при 1000-2000rps, легко получить много запросов с одного IP через мобильных провайдеров. Так что rate limit тоже вариант такой себе.

    3. Не надо делать if с перечислением, сделай лучше map. Когда надо будет добавить 16,17,... агент, задолбаешься проверять, есть ли такой уже в списке.

    4. Если уж блокировать страны, то почему бы не делать это через iptables? Неужели так нравятся 10 гиговые логи nginx?

    Если бы так легко было защититься от ддос, его бы уже не существовало. И уж тем более не существовало бы платных решений, типа ddos-guard.

    • Gacblk
      /#24340968

      Данная статья более подходит не для крупных проектов. На своём опыте могу поделиться, что многие страдают проблемой нагрузки сервера от большого количества поисковых ботов. Для проекта их уровня на данный момент нет смысла покупать платные решения. Остаются лишь варианты создания уровней блокировки трафика своими силами, не каждая площадка в интернете имеет 1000-2000rps.

      Для нормального сайта, с хорошей посещаемостью, даже при 1000-2000rps, легко получить много запросов с одного IP через мобильных провайдеров. Так что rate limit тоже вариант такой себе.

      Данный способ показан лишь как один из вариантов реализации, как вы его осуществите уже зависит от вас.

      Не надо делать if с перечислением, сделай лучше map. Когда надо будет добавить 16,17,... агент, задолбаешься проверять, есть ли такой уже в списке.

      Если вас так пугают крупные логи, то вам поможет настройка ротации логов.

      Если уж блокировать страны, то почему бы не делать это через iptables? Неужели так нравятся 10 гиговые логи nginx?

      • darken99
        /#24342918

        На ваш детский проект пришел поток трафика > 1Gbps, все ваши танцы бесполезны - у вас забит канал.
        Если хотелось написать статью то не называйте ее "защититься от DDoS" у вас мануал rate-limit для приложения на бекенде

  4. paulz1
    /#24340472

    Наверное немного странный вопрос, потому что я конечно, могу и сам документацию почитать. Но все-таки раз уж вы пишите статью…

    1) limit_req_zone $binary_remote_addr zone=shield:10m rate=10r/s;

    2)rate=10r/s /// количество requests в секунду, в данном случае 10 запросов в секунду

    3) Отсюда мы видим, 1 запрос получил 200-й код ответа, после чего все остальные запросы получали 503-й код, говорящий о том, что сервер не готов обработать данный запрос. Прекрасно — мы на верном пути.

    Не могли бы вы пояснить логику? Из написанного в пунктах 1 и 2 ожидаешь, что 10 запросов можно отправлять, и «отлуп» начнется с 11 запроса.

    • Gacblk
      /#24340970

      Да вы правы, спасибо. Данный момент был упущен. Скорректировали.

  5. pae174
    /#24341926 / +1

    | awk '{print $1}'|

    Это какой-то оверкилл. Для вырезания первой колонки по разделителю-пробелу есть:

    | cut -d ' ' -f 1 |

  6. lryzhik
    /#24342354

    nginx все-таки настоятельно рекомендует не if, а map. можете прокомментировать, почему был выбран if?

    • Gacblk
      /#24342676

      Уже писал выше, что это лишь один из вариантов реализации. Никто не запрещает его использовать как и применять map.

      • JPEGEC
        /#24344300

        Для локейшн контекста авторы фактически запрещают. Или для вас запрет это только когда технически невозможно?

        Копируем стандартный jail.conf и сохраняем его в jail.local:

        Просто создаем такой файл, копировать в него содержимое стандартного совершенно не зачем.

        logpath = /var/log/nginx/*error.log

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

        Лучше все же указывать конкретный файл.