Thank you for reading this post, don't forget to subscribe!
есть сервер 2 ярда, 8 гигов оперативы и ssd диск.
На сервере развернут wordpress сайт с некоторым содержимым. И есть у нас вредитель, который на своем сервере запускает простой тест от apache на производительность веб сервера:
1 |
# ab -c 50 -n 30000 "https://hl.zeroxzed.ru/" |
Всего лишь 50 параллельных потоков. мы видим на своем веб сервере рос LA
Сервер загружен на 100%. И хотя он нормально обрабатывает запросы и в целом корректно работает. Даже не очень тормозит, но все равно это плохо. А если будет 3 сервера и по 100 потоков на каждом? Нет никаких проблем даже на тест взять у разных хостеров по виртуальной машине и запускать на них подобные штуки, имитируя ддос атаку.
В общем, если вы совсем не сделали никакой защиты на своем сервере, то любой человек сможет вам без особых проблем доставить некоторые неудобства. Защититься от такой «атаки» не сложно. Дальше я расскажу как это сделать.
Защита от ddos с помощью iptables
Для защиты от простейшей атаки мы будем использовать firewall — iptables, модуль ядра ipset для хранения больших списков ip и самописные скрипты. По фаерволу смотрите мою статью — настройка iptables. Здесь я не буду на этом останавливаться.
Вопрос настройки ipset я подробно рассматривал в своей статье по блокировке ботов по странам. Советую посмотреть материал, так как он напрямую связан с этой статьей и дополняет ее.
Итак, приступим к созданию нашей простой защиты от dos атаки с большим количеством подключений с одного ip адреса. Для начала проверим команду, которая покажет нам количество подключений с каждого ip адреса:
1 |
# netstat -ntu | awk '{print $5}' | grep -vE "(Address|servers|127.0.0.1)" | cut -d: -f1 | sort | uniq -c | sort -n| sed 's/^[ \t]*//' |
У меня получается примерно так. Много единичных подключений. Идет штатная работа веб сервера, никто на него не ломится десятками подключений. Теперь нагрузим наш сервер множественными паразитными запросами и еще раз посмотрим вывод команды.
в самом низу вывода этой команды будет нарушитель, пытающийся организовать дос атаку на наш сервер. Теперь нарисуем скрипт, который будет блокировать всех кто устанавливает более 50-ти одновременных соединений с сайтом.
1 2 3 4 5 6 7 8 9 10 11 |
#!/bin/sh netstat -ntu | awk '{print $5}' | grep -vE "(Address|servers|127.0.0.1)" | cut -d: -f1 | sort | uniq -c | sort -n| sed 's/^[ \t]*//' | awk '{if ($1 > 50 ) print$2}' > /root/ddos/much_conn.txt sleep 3 list=$(cat /root/ddos/much_conn.txt) for ipnet in $list do ipset -A much_conn $ipnet done |
В принципе, комментировать тут особо нечего. Берем список подключений, который только что вывели, в нем сравниваем первую колонку, если она больше 50, то результат второй колонки, где записан ip адрес, передаем в файл.
Далее читаем этот файл и добавляем все ip адреса из него в ipset список под названием much_conn. Предварительно его надо создать. Подробно об этом я рассказывал в статье, на которую привел ссылку выше, но повторю еще раз здесь:
1 |
# ipset -N much_conn iphash |
Посмотреть содержимое списка можно командой:
1 |
# ipset -L much_conn |
Теперь нужно добавить в iptables правило, по которому будут блокироваться все подключения из указанного списка ipset.
1 |
# iptables -A INPUT -m set --match-set much_conn src -j DROP |
Все, мы заблокировали всех, кто создает массовый спам подключений к серверу. Ограничение в 50 подключений можете исправлять по месту, возможно его нужно будет уменьшить, если кто-то будет открывать меньше подключений с одного ip.
Единственный момент, о котором хочу сказать. Сам я не проверял, сколько подключений открывают поисковые боты, когда приходят на сайт. Я подозреваю, что явно не 50 и даже не 30, но наверняка я не проверял. В общем, будьте аккуратны, когда используете это средство.
Данный скрипт можно засунуть в крон и запускать каждую минуту. Но лично я бы так не стал делать. Я рекомендую мониторить ресурсы сервера и запускать подобные средства, только если сервер работает на пределе своих возможностей и вы вручную зашли и убедились, что вас кто-то спамит подключениями. После этого врубайте на какое-то время данный скрипт по крону. Когда ddos прекратится, отключайте.
Было бы неплохо как-то автоматически очищать список забаненных, удаляя оттуда тех, кто уже сутки к вам не подключается, но это сильно усложняет задачу. Нужно как минимум вести лог по блокирующему списку, сохранять время последнего обращения. Обрабатывать все это, высчитывать. В общем, задача хоть и не сильно сложная, но уже не тривиальная. Мне не захотелось этим заниматься.
Есть хоть и не очень изящное, но простое решение этой проблемы. Создать список ipset с заданным временем жизни записи с помощью timeout. Например вот так:
1 |
ipset -N much_conn iphash timeout 3600 |
В данном случае запись с забаненным ip в списке ipset будет храниться в течении 3600 секунд или 60 минут.
Нужно понимать, что в данном примере с 1 ip адресом использовать ipset нет никакого смысла, можно сразу банить средствами самого iptables. Ipset нужен только тогда, когда этот список хотя бы в сотни строк. Если там несколько десяткой адресов, хватит и одного iptables.
Анализ лог файла web сервера для защиты от ddos
Рассмотрим еще один простой, но все же более сложный тип ддос атаки, когда идут типовые запросы с разных IP. То есть простой ботнет, может быть даже собранный руками из нескольких дешевых vds серверов. Одновременных подключений будет не много, но если у вас тяжелый сайт и злоумышленник найдет его слабое место (например поиск), то этого может быть достаточно, чтобы положить сайт.
Банить будем тоже через iptables, а список адресов для бана будем извлекать из логов веб сервера. Для этого у вас должно быть включено логирование запросов к веб серверу. Например, в nginx за это отвечает такая настройка виртуального хоста:
1 |
access_log /web/sites/hl.zeroxzed.ru/log/access.log main; |
Мы не будем каждый раз анализировать весь лог файл. Эта операция сама по себе будет сильно нагружать веб сервер. Возьмем последние 1000 строк из лог файла и посчитаем количество подключений с одного ip с типовым содержимым, например запрос главной страницы по протоколу http 1.0, «GET / HTTP/1.0». Если вы заметите другой постоянный признак ботнета, который вас атакует, используйте его. Это может быть один и тот же user agent или что-то еще. Допустим, если атакующий будет долбить в уязвимое место, то это будет адрес этой страницы.
1 |
# tail -1000 /web/sites/hl.zeroxzed.ru/log/ssl-access.log | egrep "GET / HTTP/1.0" | awk '{print $1}' | sort -n | uniq -c |
В данном случае я использовал немного другое условие и просто вывел список всех тех, кто стучался на главную страницу. Но уже тут видно нарушителя, которого можно забанить.
Рисуем похожий на предыдущий скрипт для автоматической блокировки тех, кто отправляет слишком много запросов на наш сайт и создает проблемы с производительностью. Повторюсь еще раз, если проблем с производительностью нет, я не рекомендую делать лишних движений.
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/sh tail -1000 /web/sites/hl.zeroxzed.ru/log/ssl-access.log | egrep "GET / HTTP/1.0" | awk '{print $1}' | sort -n | uniq -c | sort -n | tail -n100 | awk '{if ($1 > 50 ) print $2}' > /root/ddos/much_gets.txt sleep 3 list=$(cat /root/ddos/much_gets.txt) for ipnet in $list do ipset -A much_gets $ipnet done |
Здесь делаем то же самое, что и раньше. Те, кто сделали более 50-ти одинаковых запросов по нашей маске на последние 1000 строк в лог файле, отправляются в бан.
Не забудьте создать отдельный список в ipset и добавить отдельное правило в ipables. Можно использовать уже существующий список и добавленное правило из предыдущего примера, но я рекомендую все разделять. Так удобнее для последующего анализа.
Во время ddos атаки добавляете это правило в cron и выполняете каждую минуту. После завершения атаки скрипт можно отключить. В принципе, можно и на постоянку оставлять, но тут нужно хорошенько подумать и прикинуть, как оно должно выглядеть. Главный принцип — не навредить.
Баним ботов с неправильным referer
Есть категория простых ботов, которые подставляют в referrer либо мусор, либо кривые урлы без http или https в начале. Например вот такие:
1 |
194.67.215.242 - - [30/Nov/2017:20:48:08 +0300] "POST /index.php HTTP/1.1" 200 913 "<strong>g0dfw4p1.ru</strong>" "Mozilla/5.0 (Windows NT 6.0; rv:34.0) Gecko/20100101 Firefox/34.0" "-" |
Корректное поле referer должно содержать либо http, либо https, либо быть пустым. Все, что иначе, можно смело блокировать или возвращать статус ошибки. Добавляем примерно такую конструкцию в конфигурацию виртуального хоста, в раздел server {}.
1 2 3 |
if ($http_referer !~* ^($|http://|https://) ) { return 403; } |
После этого проверьте конфигурацию nginx и перечитайте ее.
1 2 |
# nginxt -t # nginx -s reload |
Если вас достает какой-то бот с конкретным referer, можно забанить именно его. Для этого можно дополнить условие, или изменить. Например, вот так:
1 2 3 |
if ($http_referer = "https://bots.ru/dostanim_tebya.html") { return 403; } |
В дополнение, можно всех этих ботов с помощью простого скрипта банить на iptables, как в примерах выше. К слову сказать, их можно банить сразу, разбирая http запросы еще до того, как они будут попадать к nginx, например, с помощью ngrep, но это более сложная задача. Не все это умеют делать, там есть нюансы, а с nginx знакомы все. Не составит большого труда реализовать данный метод.
Защита от ддос с помощью модулей nginx — limit_conn и limit_req
Поделюсь еще одним простым способом снизить нагрузку на сервер и частично защититься от ддос с помощью модулей nginx — limit_conn и limit_req. Настроить их не сложно, частично результат работы первого модуля будет пересекаться с первыми двумя способами ddos защиты, описанными в начале. Он более простой для настройки, так что если не справились с теми способами, можно попробовать этот.
Смысл данных модулей в том, что один может ограничить одновременное количество разрешенных соединений с сайтом, а другой количество соединений в единицу времени.
Я ограничу в своем примере количество одновременных подключений к сайту с одного ip числом 50, а количество одновременных запросов к динамическому контенту не более 2-х в секунду. При этом будет разрешен всплеск (burst) запросов до 5-ти. Объясню, как понимать этот всплеск, так как сам не сразу понял, что конкретно он означает.
Если у нас идет превышение количества установленных запросов в секунду, то их выполнение задерживается, и они выстраиваются в очередь на исполнение с указанной скоростью. Размер этой очереди и равен значению всплеска. Все запросы, которым не хватит места в очереди, будут завершены с ошибкой. То есть, если запросов будет 4 в секунду, то 2 выполнятся сразу и еще 2 встанут в очередь. А если будет 10, то 2 выполнятся сразу, 5 встанут в очередь на выполнение по 2 штуки в секунду, а остальные будут завершены с ошибкой.
Исходя из этих условий, ограничение на подключения нужно установить в контексте server, а на доступ к динамическому контенту в соответствующем location. При этом описание зон, которые будут использовать директивы, нужно расположить в http.
Вот пример конфига nginx для реализации установленных ограничений с целью защиты от ддос атак.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
http { ... limit_conn_zone $binary_remote_addr zone=perip:10m; limit_req_zone $binary_remote_addr zone=dynamic:10m rate=2r/s; ... server { ... limit_conn perip 50; ... location ~ \.php$ { ... limit_req zone=dynamic burst=5 nodelay; ... } } } |
После этого перезапустите nginx и проверьте как работают лимиты. Ограничение на количество выполняемых динамических запросов можно увидеть, просто нажимая очень быстро F5 в браузере. Если будете достаточно быстры, то скоро увидите картинку с 503 ошибкой
и запись в логе с ошибками:
1 |
2017/11/30 15:25:26 [error] 9773#9773: *51482 limiting requests, excess: 5.664 by zone "dynamic", client: 195.91.248.43, server: hl.zeroxzed.ru, request: "GET / HTTP/2.0", host: "hl.zeroxzed.ru", referrer: "https://hl.zeroxzed.ru/2013/03/15/featured-image-vertical/" |
Лимит на количество подключений можете проверить той же утилитой ab, о которой я рассказал во введении.
1 |
017/11/30 15:38:56 [error] 9773#9773: *53938 limiting connections by zone "perip", client: 94.142.141.246, server: hl.zeroxzed.ru, request: "GET /wp-content/uploads/2013/03/the-dark-knight-rises.jpg HTTP/1.0", host: "hl.zeroxzed.ru" |
Только не забывайте, что тест нужно запускать не на конкретную страницу, тогда вы попадете на ограничение выполнения динамического контента, а на что-то другое. Например, как в моем примере, на картинку.