Однажды мой знакомый написал мне, что его сайт перестал открываться сказал, что на сайт обрушилась 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 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 /usr/share/nginx/html;
}

# Deny access to .htaccess files, if Apache's document root concurs with nginx's one
location ~ /\.(ht|hg|git|svn) { 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 true; do
netstat_str=$(netstat -an)
        echo -n 'SYN_RECV: '
        echo "$netstat_str" | grep 80 | grep -c SYN_RECV
        echo -n 'TIME_WAIT: '
        echo "$netstat_str" | grep 80 | grep -c TIME_WAIT
        echo -n 'FIN_WAIT: '
        echo "$netstat_str" | grep 80 | grep -c FIN_WAI1
        echo -n 'ESTABLISHED: '
        echo "$netstat_str" | grep 80 | grep -c ESTABLISHED

        echo -n 'BLOCKED_IP to Black-route: '
        ip route list | grep -c blackhole

        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 640 nginx adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
    endscript
}

Поменялся метод ротации вместо daily теперь используется size 1M, это позволит уменьшить объем лог файла, т.к. за сутки он может очень сильно вырасти в объеме, что замедлит парсинг и увеличит нагрузку на сервер, а использование ротации по размеру исправит эту ситуацию.

Еще я использовал встроенные средства 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, когда список заблокированных вырос сайт начал свою штатную работу, а список блокированных с течением времени увеличивался все медленнее и в итоге совсем перестал расти - у атакующего закончились боты.


Comments

comments powered by Disqus