DDoS — различия между версиями

Материал из OpenWiki
Перейти к: навигация, поиск
(Разные скрипты и настройки)
(опечатка)
 
(не показаны 3 промежуточные версии 3 участников)
Строка 1: Строка 1:
 
= Опыт противостояния DDoS атаке =
 
= Опыт противостояния DDoS атаке =
  
В результате DDoS атаки было собран блэклист из участвующих в атаке примерно 140 тысяч IP и 400 западных подсетей по маске /16.  
+
В результате DDoS атаки был собран блэклист из участвующих в атаке примерно 140 тысяч IP и 400 западных подсетей по маске /16.  
 
Представленные примеры справедливы для ОС FreeBSD 6.x, которая была установлена на подвергшемся атаке сервере. В материале даются только общие рекомендации, о том как настроить FreeBSD для обработки нескольких десятков тысяч одновременных запросов можно прочитать [http://www.opennet.ru/base/net/tune_freebsd.txt.html здесь].
 
Представленные примеры справедливы для ОС FreeBSD 6.x, которая была установлена на подвергшемся атаке сервере. В материале даются только общие рекомендации, о том как настроить FreeBSD для обработки нескольких десятков тысяч одновременных запросов можно прочитать [http://www.opennet.ru/base/net/tune_freebsd.txt.html здесь].
  
Строка 60: Строка 60:
 
в кэш могут попасть некорректные данные.
 
в кэш могут попасть некорректные данные.
  
Переренаправление делаем средствами JavaScript, вместо редеректа через HTTP
+
Перенаправление делаем средствами JavaScript, вместо редеректа через HTTP
заголовок. Помогает для отсеивания роботов, которые прекрасно обрабатываеют
+
заголовок. Помогает для отсеивания роботов, которые прекрасно обрабатывают
 
переброс по "302 Location:".
 
переброс по "302 Location:".
  
Строка 68: Строка 68:
  
 
Далее по логу http-сервера строим список блокировки, выявляя IP запросившие / больше N раз, но
 
Далее по логу http-сервера строим список блокировки, выявляя IP запросившие / больше N раз, но
не перешедшие на /idx.html. Можно также учитывать запрос кратинки,
+
не перешедшие на /idx.html. Можно также учитывать запрос картинки,
 
но есть вероятность заблокировать клиентов с отключенной загрузкой картинок.
 
но есть вероятность заблокировать клиентов с отключенной загрузкой картинок.
 
Поэтому лучше оценивать доп. загрузки .css или .js файлов, если не для них не выставлен большой "expires".
 
Поэтому лучше оценивать доп. загрузки .css или .js файлов, если не для них не выставлен большой "expires".
 
  
 
=== Варианты http-флуда ===
 
=== Варианты http-флуда ===
Строка 138: Строка 137:
  
 
pid=`cat ${log_dir}/nginx.pid`
 
pid=`cat ${log_dir}/nginx.pid`
 
#test
 
echo "Moving ${log_dir}/access.log to ${log_dir}/access.${date}.log"
 
echo "Sending USR1 to nginx with pid ${pid}"
 
echo "Archiving ${log_dir}/access.${date}.log"
 
  
 
mv ${log_dir}/access.log ${log_dir}/access.${date}.log
 
mv ${log_dir}/access.log ${log_dir}/access.${date}.log
Строка 163: Строка 157:
 
#!/usr/bin/perl
 
#!/usr/bin/perl
 
# Блокируем если более 5 запросов "/" без запроса '/idx.html' или при запросе маски '/search/?str=...'
 
# Блокируем если более 5 запросов "/" без запроса '/idx.html' или при запросе маски '/search/?str=...'
my %flag_hash=();
+
my %flag_hash;
 
while(<>){
 
while(<>){
     my ($s_ip,undef,undef,undef,undef,$s_cmd,$s_req,undef) = split(/\s/,$_);
+
     my ($s_ip,undef,undef,undef,undef,$s_cmd,$s_req,undef) = split;
 
     if ($s_req eq '/' ){
 
     if ($s_req eq '/' ){
         if (! defined $flag_hash{$s_ip}){
+
         if (! exists $flag_hash{$s_ip}){
 
             $flag_hash{$s_ip}=1;
 
             $flag_hash{$s_ip}=1;
 
         } elsif ($flag_hash{$s_ip} > 0){
 
         } elsif ($flag_hash{$s_ip} > 0){
Строка 178: Строка 172:
 
     }
 
     }
 
}
 
}
foreach my $ip (keys %flag_hash){
+
while (my ($ip, $val) = each %flag_hash){
     if ($flag_hash{$ip} < 5){next;}
+
     next if $val < 5;
     print "$flag_hash{$ip} $ip\n";
+
     print "$val $ip\n";
 
}
 
}
  

Текущая версия на 17:59, 19 сентября 2011

Опыт противостояния DDoS атаке

В результате DDoS атаки был собран блэклист из участвующих в атаке примерно 140 тысяч IP и 400 западных подсетей по маске /16. Представленные примеры справедливы для ОС FreeBSD 6.x, которая была установлена на подвергшемся атаке сервере. В материале даются только общие рекомендации, о том как настроить FreeBSD для обработки нескольких десятков тысяч одновременных запросов можно прочитать здесь.

Первая стадия атаки

Наводнение UDP пакетами с интенсивностью забивающей внешний канал локального провайдера. Так как кроме web-сервера на атакуемой машине не было других сервисов и локальный rate-limit на UDP делать бесполезно, было принято решение по блокированию всего UDP трафика на атакуемый IP на маршрутизаторе магистрального оператора связи.

Вторая стадия атаки: HTTP наводнение

Различные попытки флуда по HTTP. Методы борьбы см. ниже.

Третья стадия атаки: SYN-flood

Наводнение большим колличеством SYN пакетов (SYN-flood). Машина справлялась нормально, но установленный rate-limit на машрутизаторе привел к 50% потере пакетов.

Решение: выявление через анализ лога tcpdump и блокирование на маршрутизаторе особенно агрессивных подсетей.

Для блокирования адресов участвующих в SYN-flood атаке можно использовать скрипт в котором в цикле запускается tcpdump с опцией "-c N" позволяющей завершить выполнение после получения N пакетов. Затем в скрипте вызывается код анализирующий число SYN запросов с одного IP и при значительном преобладании SYN пакетов, заносящего адрес в черный список.

Следует обратить внимание:

  • необходимо позаботиться об исключении ситуации повторного блокирования адресов. Если IP ранее заблокирован, то его динамика запросов в логе tcpdump будет напоминать SYN-flood.
  • исключение блокирования валидных адресов при нарушении функционирования сервиса. Порог блокировки должен быть достаточно высок.


Четвертая стадия: DNS-флуд

Флуд DNS серверов через наводнение запросами на резолвинг доменов состоящих из случайного набора символов (например, csdcscsaxas.org). 70% трафика была с фиктивного IP (спуфинг), совпадающего с IP атакуемого сайта, в надежде, что на маршрутизаторе не запрещен прием пакетов с локальным входящим IP, приходящих через внешний интерфейс. В таком случае, DNS сервер не блокировал бы рекурсивные запросы и отправлял ответ на IP сайта. Для внешних IP рекурсивные запросы были запрещены, но и при генерации "отлупов" DNS серверам пришлось не сладко, хотя больше пострадал внешний канал, который был полностью забит DNS-запросами.

Использованные методы противостояния HTTP-флуду

Минимизация трафика: Предположим, флуд направлен на запрос "/", запросы приходят с неотличимыми от реальных пользователей "User Agent" и "Referrer". Вместо / подставляем redir.html:

 <html>
   <head>
   <script>
       document.location='http://www.site.ru/idx.html';
   </script>
   </head>
   <body>
   </body>
   </html>

Затем /idx.html перенаправляем на предкешированный в статику индекс, который обновляем, например, раз в 10 мин. Предусмотреть ситуацию когда backend не сможет выполнить запрос (например, в момент изменения тактики флуда) и в кэш могут попасть некорректные данные.

Перенаправление делаем средствами JavaScript, вместо редеректа через HTTP заголовок. Помогает для отсеивания роботов, которые прекрасно обрабатывают переброс по "302 Location:".

Другой вариант, установка при запросе "/", cookie через http header и дальнейшее блокирование запросов /idx.html пришедших без cookie.

Далее по логу http-сервера строим список блокировки, выявляя IP запросившие / больше N раз, но не перешедшие на /idx.html. Можно также учитывать запрос картинки, но есть вероятность заблокировать клиентов с отключенной загрузкой картинок. Поэтому лучше оценивать доп. загрузки .css или .js файлов, если не для них не выставлен большой "expires".

Варианты http-флуда

Запросы только одной страницы (запрашивают только индекс или определенную страницу)

  • Страница всегда одна и та же (запрашивают только /).
  • Со временем страница меняется (запрашивают /, потом /search, потом /news)
    • Определение частоты запросов одного URI и дальнейший перевод его в статику.
    • Предусмотреть возможность запроса одного ресурса с разными параметрами, например, открытие страницы поиска с разными ключевыми словами.

Запросы с правильной цепочкой открытия страниц (/ => /idx).

  • При каждом запросе отличаются "User Agent", можно использовать этот факт (с одного IP приходят несколько запросов с разными User Agent). Но нужно быть осторожным и не заблокировать по ошибке работающих через NAT
  • Превышение допустимого числа запросов в ед. времени: если больше 10 запросов одной страницы в минуту - блокируем.

Написание скрипта для автоматизации

Cкрипт для построения черного списка через периодический анализ лог файла HTTP сервера nginx:

  1. Блокируем, если с данного IP поступило более N запросов одного рабочего URL и не поступило ни одного запроса транзитного URL, из которого осуществляется переброс на рабочий URL.
  2. Блокируем, если с данного IP поступило более M запросов URL на который направлен флуд.
  3. Блокируем, если с данного IP поступило более I запросов контентообразующего URL, но не было ни одного запроса CSS, JS, GIF, JPG и прочих связанных с данным URL файлов.
  4. Автоматически определяем список URL на которые направлена атака и осуществляем предкэширование их в статику, с промежуточным пробросом через html заглушку-редирект малого размера.

Общие наблюдения

  • Если очень много запросов приходят со странных и редких user agent. Можно блокировать.
  • Драконовские методы:
    • Построения списка подсетей /16 по большому числу запросов из них от разных IP.
    • Если трафик в основном местный, подойдет подключение Geo базы с временным блокированием иностранных подсетей.
  • Выявление злоумышленника инициировавшего DDoS атаку (надежда на то, что атакующий ошибется и засветит свой IP).
    • Слежение за ICMP echo трафиком.
    • Выявление странных и типовых проверок, предшествующих очередной волне атаки.
    • Введение в заблуждение "странных" IP (например, только для определенного адреса выдавать другие данные в DNS) и наблюдение за реакцией по динамике флуда.
  • В системе неплохо предусмотреть выполнение watch-dog деглюкера, запрещающего все кроме родной сети при большой патере пакетов. Нужен чтобы можно было зайти по SSH и проанализировать ситуацию. Блокировать и разрешать только вход администратора, если невозможно загрузить внешний URL с флаговым документом в течении N попыток, как только флаговый документ загрузился - отменять блокировку.
  • Выявление фиксированной маски, типичной для данной атаки. Например, странные User Agent, отсутствие Referer или его несответсвтие истинному положению

(например, промежуточная страница-редирект перенаправляет на www.host.ru, а в referer постоянное значение без www), разные User Agent для запроса страницы и последующего запроса сопутствующих файлов.

Разные скрипты и настройки

Скрипты и настройки могут быть неоптимальными, так как написаны в условиях катастрофической нехватки времени. Но они реально помогли противостоять атаке.

block_ipfw.sh - скрипт для быстрой загрузки черного списка, выполняется как "cat черный_список.txt| ./block_ipfw.sh"

  #!/bin/sh
  cat - | while read cnt ip; do
     echo ipfw table 2 add $ip 1
     ipfw table 2 add $ip 1
  done

Для составления списка /16 подсетей по черному списку IP можно использовать:

  cat block1.txt block2.txt block3.txt | awk '{print $2}' | awk -F \. '{print $1 "." $2 ".0.0/16" }' | sort | uniq -c | sort -gr | head -n 300 > ./block_net.txt

Для разбора лога tcpdump:

  cat tcpdump.log |awk '{print $3}'|cut -d'.' -f1,2,3,4|sort|uniq -c |sort -r -n|grep -vE "^111.222"

flood_rotate.sh - скрипт для ротации лога nginx во время флуда с автоматическим обновлением блэклиста (запускается из crontab с периодичностью 1-10 мин).

#!/bin/sh
date=`date +"%Y%m%d-%H%M"`

log_dir=/usr/local/nginx/logs
cd ${log_dir}

pid=`cat ${log_dir}/nginx.pid`

mv ${log_dir}/access.log ${log_dir}/access.${date}.log
kill -USR1 ${pid}
# ниже "111.222" - локальная подсеть, фильтруется чтобы не блокировать по ошибке локальных клиентов
./grep_flood_ip.pl ${log_dir}/access.${date}.log |grep -vE "^111.222" > ${log_dir}/last_flood_ip.log
cat ${log_dir}/last_flood_ip.log >> /etc/block1.txt

cat ${log_dir}/last_flood_ip.log | while read cnt ip; do
    echo ipfw table 1 add $ip 1
    /sbin/ipfw table 1 add $ip 1
done

/usr/bin/gzip ${log_dir}/access.${date}.log

./grep_flood_ip.pl - скрипт для выявления участвующих во флуде IP на основе анализа лога. (В процессе работы был создан еще один сткрипт для отсеивания на основании непостоянства User Agent при запросах с одного IP, но этот скрипт где-то потерялся. Принцип работы тот же, но скрипт более требователен к памяти, так как кроме IP держит в памяти еще и User Agent)

#!/usr/bin/perl
# Блокируем если более 5 запросов "/" без запроса '/idx.html' или при запросе маски '/search/?str=...'
my %flag_hash;
while(<>){
    my ($s_ip,undef,undef,undef,undef,$s_cmd,$s_req,undef) = split;
    if ($s_req eq '/' ){
        if (! exists $flag_hash{$s_ip}){
            $flag_hash{$s_ip}=1;
        } elsif ($flag_hash{$s_ip} > 0){
            $flag_hash{$s_ip}++;
        }
    } elsif ($s_req eq '/idx.html'){
         $flag_hash{$s_ip}=0;
    } elsif ($s_req eq '/search/?str=...'){
         $flag_hash{$s_ip}=999;
    }
}
while (my ($ip, $val) = each %flag_hash){
    next if $val < 5;
    print "$val $ip\n";
}


/boot/loader.conf

vm.kmem_size=500000000
net.inet.tcp.tcbhashsize=2048
net.inet.tcp.syncache.hashsize=1024
net.inet.tcp.syncache.bucketlimit=100
kern.ipc.nmbclusters=131072
kern.maxdsiz="1610612736"
kern.dfldsiz="1610612736"
kern.maxssiz="1610612736"

/etc/sysctl.conf:

kern.ipc.somaxconn=8192
kern.maxfiles=100000
kern.maxfilesperproc=81000

kern.ipc.maxsockets=80000
net.inet.tcp.maxtcptw=90960
net.inet.tcp.delayed_ack=0
net.inet.udp.maxdgram=57344

# все пакеты на закрытый порт отбрасываются без отсылки RST
net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1

#максимальное количество  ICMP Unreachable и TCP RST пакетов в секунду
net.inet.icmp.icmplim=400

net.local.stream.recvspace=8192
net.local.stream.sendspace=32768

# На прием нет необходимости в большом буфере
net.inet.tcp.recvspace=8192
net.inet.udp.recvspace=8192

# На отправку 32k достаточно.
net.inet.tcp.sendspace=32768

net.inet.ip.fw.dyn_keepalive=0
net.inet.tcp.msl=7500
kern.polling.enable=1

# Чтобы избежать tamewait-ов при запросе к бэкенду
net.inet.tcp.nolocaltimewait=1

net.inet.ip.portrange.randomized=0
net.inet.ip.portrange.first=10000

net.inet.ip.fw.dyn_max=40000

nginx.conf (часть конфига):

     location = / {
        if ($args = %2F){
            # Блокируем сразу, по маске параметров '%2F'
            return 403;
            break;
        }
        if ($http_user_agent ~ (inktomi.com|avastye.com|H010818|ODI3|FunWebProducts|looksmart.net|i.love.teh.cock|googlebawt.com|Microsoft-WebDAV-MiniRedir) ) {
            Блокируем сразу кривые User Agent
            return 403;
            break;
        }

        root   /usr/local/apache/htdocs;
        #if (-f $document_root/redir.html) {
            # Вместо индекса выдаем статику с кодом переброса.
            rewrite ^.*$ /redir.html break;
        #}
    }
    location = /idx.html {
      # Второй уровень проброса
      rewrite ^.*$ /idx2.html break;
    }
    location = /idx_old.html {
       # Блокируем старый URL проброса, который раскусили боты.
       return 403;
       break;
    }
   

store_log.sh - скрипт для вывода разной системной статистики разом

#!/bin/sh
# Динамика:
#netstat -w 1
#iostat -w 1
#vmstat -w 1
#gstat

echo net.inet.tcp.syncache
sysctl net.inet.tcp.syncache

echo "======================================"
echo vmstat
vmstat -z
vmstat -i
vmstat

echo "======================================"
echo iostat
iostat

echo "======================================"
echo netstat
netstat -m
netstat -Lan
netstat -s -p tcp
#netstat -s -p udp

echo "======================================"
echo sysctl net.inet.ip.fw.*dyn*
sysctl net.inet.ip.fw|grep dyn

echo "======================================"
echo "counters (connection/stablished/open sockets/open files)"
netstat -an|wc
netstat -an|grep ESTAB|wc
sysctl kern.ipc.numopensockets
sysctl kern.openfiles