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