Категории

четверг, 9 августа 2012 г.

Борьба с DDoS SYN-Flood штатными средствами и nginx в Linux

Однажды мой знакомый написал мне, что его сайт перестал открываться сказал, что на сайт обрушилась DDoS атака. Полез я разбираться, в чем дело и в итоге суть проблемы была найдена - куча ip адресов с различными User Agent'ами приходили на сайт и делали очень много различных запросов...
В общем нужно было этих ботов отлавливать и блокировать им доступ к сайту, сперва подумал об iptables и ConfigServer Firewall (csf), но хостинг был внутри OpenVZ, а это означало невозможность использования более 120 правил, а так же невозможность установки модулей ядра и вообще каких либо изменений опций ядра. Потому стандартные подходы для решения проблемы сразу отпадали.
А время шло, и переписываться с тех поддежкой хостинга небыло времени, а платить огромные суммы за защиту от такой примитивной атаки небыло никакого желания.
Решение пришло в голову неожиданно, при перечитывании man ip, вспомнить зачем я его читал затрудняюсь, но решение было на редкость извращенским, но вполне рабочим :)
Решением было добавление маршрута проблемного IP в blackhole. В следствие чего пакеты от этих адресов будут молча отбрасываться (the rule prescribes to silently drop the packet.).
Сперва конфигурируем nginx:
...
worker_rlimit_nofile 200000;
events {
        worker_connections 1024;
        use epoll;
}

....

http {
        # Документ по умолчанию
        index  index.html index.htm index.php;

        ##
        # Basic Settings
        ##
        sendfile on;
        send_timeout 5;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 30 15;
        types_hash_max_size 2048;
        server_tokens off;

        client_header_timeout 15;
        client_body_timeout 15;

        #  лимитируем для зоны one 10 конектов в 1 сек.
        limit_req_zone $binary_remote_addr zone=one:16m rate=10r/s;

        #В ситуации когда сервер записал в сокет данные, но клиент не хочет
        #       их забирать, после таймаута по закрытию соединения в ядре
        #       данные будут держаться еще несколько минут. В nginx если директи                                                                             ва
        #       для принудительного сброса всех данных после закрытия по таймаут                                                                             у.
        reset_timedout_connection  on;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # Logging Settings
        ##
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        gzip on;
        gzip_disable "msie6";
...
server {
        listen ip.ip.ip.ip:80;
        server_name my.site;

        open_file_cache max=100000 inactive=40s;
        open_file_cache_valid 60s;
        open_file_cache_min_uses 2;
        open_file_cache_errors on;

        #это значит что законектится с лимитом в 3 подключения за 1 сек можно 3 раза, а дальше 503 ошибка. Что и пишется в лог access.
        limit_req zone=one burst=10;

        # Default locations config.
        include conf.std;
...
}
...
}
Файлик /etc/nginx/conf.std:
####### STANDART
# redirect server error pages to the static page /50x.html
error_page   500 502 503 504  /50x.html;
        location = /50x.html {
        root   /var/www/nginx-dist;
}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
location ~ /\.ht {
        deny  all;
}

location ~ /\< {
        deny  all;
}
С конфигурацией nginx все, теперь скрипты.
Добавляем в файл /etc/crontab строки:
# parse nginx logs and ban bad ip via nullroute
* * * * * root /root/ddos/parse_nginx.log.sh >/root/ddos/parse_nginx.log.log 2>&1
Содержимое скрипта /root/ddos/parse_nginx.log.sh:
#!/bin/sh

ADMINS_IP='ip.ip.ip.ip'

echo $(date)
echo '--- запускаем систему парсинга nginx лога---'

echo ' ищем ботов'
cat /var/log/nginx/access.log \
        | grep -E -e 'HTTP/1.(0|1)" (400|403|405|499|503)' -e '] "-" 400 0 "-" "-"' \
        | awk '{print $1}' \
        | sort -nr | uniq -c \
        | awk '{if($1>10)print $1" "$2}' \
    > /root/ddos/banlist.txt

cat /var/log/nginx/error.log \
        | grep -E '(limiting requests|limiting connections)' \
        | awk -F"client: " '{print $2}' \
        | awk -F"," '{print $1}' \
        | sort -nr | uniq -c \
        | awk '{if($1>10)print $1" "$2}' \
    >> /root/ddos/banlist.txt

# get unique ip
cat /root/ddos/banlist.txt \
        | grep -v $ADMINS_IP \
        | uniq | sort -nr \
    > /root/ddos/banlist_uniq.txt

echo '------  очищаем tmp file бана-'
cat /dev/null > /root/ddos/banlist.txt

echo ' создаем DROP правила для 50 самых агрессивных ботов'
awk '{print $2}' /root/ddos/banlist_uniq.txt \
        | uniq | head -n 150 > /root/ddos/banlist.txt

#т.к. iptables полнейшее УГ, особенно внутри OpenVZ, баним ip вот таким извращенским методом... через nullroute
#ip route flush type blackhole
for ip in $(cat /root/ddos/banlist.txt); do
        ip route add blackhole ${ip}/32
done

#echo 'записываем злобных ботов в csf.deny'
#cat /etc/csf/csf.deny >> /root/ddos/banlist.txt
#cat /root/ddos/banlist.txt \
#       | uniq | sort -nr \
#    > /etc/csf/csf.deny

#echo 'csf релоад, внесение в iptables ботов'
#/usr/sbin/csf -r
sleep 5

echo '--  делаем ротацию лога--------'
test -x /usr/sbin/logrotate || exit 0
/usr/sbin/logrotate /etc/logrotate.conf

echo '=====злобные боты в списке бана====='

sleep 1
Для мониторинга состояния сервера используем такой скрипт:
#!/bin/sh

while :; do
netstat_str=$(netstat -an)
        echo -n 'SYN_RECV: '
        echo "$netstat_str" | grep 80 | grep SYN_RECV | wc -l
        echo -n 'TIME_WAIT: '
        echo "$netstat_str" | grep 80 | grep TIME_WAIT | wc -l
        echo -n 'FIN_WAIT: '
        echo "$netstat_str" | grep 80 | grep FIN_WAI1 | wc -l
        echo -n 'ESTABLISHED: '
        echo "$netstat_str" | grep 80 | grep ESTABLISHED | wc -l

        echo -n 'BLOCKED_IP to Black-route: '
        ip route list | grep blackhole | sort | wc -l

        sleep 2
        echo '------------------------- for stop this script Press Ctrl+C'
done
Еще понадобится настроить ротацию логов, приводим файл /etc/logrotate.d/nginx к такому виду:
/var/log/nginx/*.log {
        size 1M
        missingok
        rotate 52
        compress
        delaycompress
        notifempty
        create 0640 www-data adm
        sharedscripts
        prerotate
                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                        run-parts /etc/logrotate.d/httpd-prerotate; \
                fi; \
        endscript
        postrotate
                [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid`
        endscript
}
Еще я использовал встроенные средства iptables для борьбы с DDoS:
#!/bin/sh

iptables -F
iptables -N syn_flood
iptables -A INPUT -p tcp --syn -j syn_flood
iptables -A syn_flood -m limit --limit 100/s --limit-burst 150 -j RETURN
iptables -A syn_flood -j DROP

Данный скрипт спокойно блокировал DDoS примерно в 10 000 ботов, при этом сайт был полностью доступен и атака вообще не чувствовалась.
Прошу обратить внимание на то, что скрипт запускается раз в минуту, и блокирует ip адреса по заданным критериям в скрипте /root/ddos/parse_nginx.log.sh. В моем случае атака была "вялой" и одной минуты вполне хватало для сбора ip адресов ботов. В первые минуты сайт "туго" работал, но спустя минут 10, когда список заблокированных вырос сайт начал свою штатную работу, а список блокированных с течением времени увеличивался все медленнее и в итоге совсем перестал расти - у атакующего закончились боты.

11 комментариев:

black13 комментирует...

10.000 ботов?
в OpenVZ?
да еще и арендованом?
И что не завалили? Аплинк у вас чтоли 10G и ядро хитрое? Со штатным ядром и таким количеством атакующих трудно не то что логику борьбы на уровне фаервола/сервиса настраивать, а просто держать канал... простая арифметика, что будет если каждый из 10.000 ботов пульнет в ваш сервак по 100kB/s трафика (по барабану какого)?

METAJIJI комментирует...

Боты не давали трафик, они делали запросы и уходили, этого хватало, чтобы сайт загибался.
Да и как бот сможет мне отправлять трафик, если я его не принимаю? :)

black_13 комментирует...

Вы то его не принимаете ... но канал у вас зашейпленый провайдером и фиксированный - например 100 мегабит/cек
Шлем вам от каждого бота по 100кбит/сек. UDP пакетов и что будет с вашим каналом 100мбит? Правильно, принимай не принимай эти пакеты, а труба забита (читай канал) "левым трафиком". Сервис при этом может даже работать, но не имея канала (его забили под завязку боты своими UDP-шками) к вам не сможет придти ни один клиент :)
Такчто вам либо повезло с атакой либо вы приврали с количеством ботов :) Знаю не по наслышке, недавно выгребал DDoS с количеством ботов около 1000 - они уложили канал 175 мбит в потолок и их цель была достигнута - сервер с мира было видно с потерями 90-95%, соответственно до сервисов достучаться было уже крайне проблематично (можно сказать что сервисы были недоступны)

METAJIJI комментирует...

Про UDP согласен, канал ляжет, но статья-то именно про Syn-Flood, который возможен только на уровне TCP протокола, трафик там копеечный и сильно не мешал работе сайта, тем более, что запросы делались не так часто, а вот когда они добирались до веб сервера, то ему становилось плохо, он упорно ждал ответа от клиентов(ботов), которые молчали как партизаны, в результате чего нормальные клиенты не могли попасть на сайт. Что касается цифр ботов, то они реальные. Список пополнялся ~50-200 ip.
Видимо зря я не упомянул в статье о других видах атак, и что эта статья от них не спасет, а только от вялой TCP SYN-Flood атаки :)

black_13 комментирует...

Согласен по поводу син флуда
Мои боты были "круче"
Если они блочились фаерволом - сразу переключались на UDP flood
Бывает и так :(

mvg комментирует...

А как теперь разбанить ипы.

METAJIJI комментирует...

Очевидно же ip route delete ip.ip.ip.ip.
Подробнее man ip, раздел про ip route :)

mvg комментирует...
Этот комментарий был удален автором.
mvg комментирует...

Спсибо разобрался сам как удалить правила из ip route

Я немного пределал под ipset, так надежней 5к записей без какой либо нагрузки на CPU

METAJIJI комментирует...

Это вам повезло, что у вас там есть ipset:) и кстати блокировка через blackhole маршрут тоже обходится без какой либо нагрузки на CPU.

black_13 комментирует...

А TARPIT в iptables ктото применял? Крутая штука поидее....

Отправить комментарий