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

Материал из OpenWiki
Перейти к: навигация, поиск
(Новая: = Опыт противостояния DDoS атаке = В результате DDoS атаки было собран блэклист из участвующих в атаке при...)
 
Строка 93: Строка 93:
 
3. Блокируем, если с данного IP поступило более I запросов контентообразующего URL, но не было ни одного запроса CSS, JS, GIF, JPG и прочих связанных с данным URL файлов.
 
3. Блокируем, если с данного IP поступило более I запросов контентообразующего URL, но не было ни одного запроса CSS, JS, GIF, JPG и прочих связанных с данным URL файлов.
 
4. Автоматически определяем список URL на которые направлена атака и осуществляем предкэширование их в статику, с промежуточным пробросом через html заглушку-редирект малого размера.
 
4. Автоматически определяем список URL на которые направлена атака и осуществляем предкэширование их в статику, с промежуточным пробросом через html заглушку-редирект малого размера.
 
 
 
  
 
=== Общие наблюдения ===
 
=== Общие наблюдения ===
Строка 111: Строка 108:
 
* Выявление фиксированной маски, типичной для данной атаки. Например, странные User Agent, отсутствие Referer или его несответсвтие истинному положению
 
* Выявление фиксированной маски, типичной для данной атаки. Например, странные User Agent, отсутствие Referer или его несответсвтие истинному положению
 
(например, промежуточная страница-редирект перенаправляет на www.host.ru, а в referer постоянное значение без www), разные User Agent для запроса страницы и последующего запроса сопутствующих файлов.
 
(например, промежуточная страница-редирект перенаправляет на 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 можно использовать:
 +
 +
  cd /etc/ ; 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 мин).
 +
 +
<pre>
 +
#!/bin/sh
 +
date=`date +"%Y%m%d-%H%M"`
 +
 +
log_dir=/usr/local/nginx/logs
 +
cd ${log_dir}
 +
 +
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
 +
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
 +
</pre>
 +
 +
./grep_flood_ip.pl - скрипт для выявления участвующих во флуде IP на основе анализа лога. Внимание, скрипт рассчитан на небольшие порции лога (не гигабайты), так как держит полный список User Agent и IP в памяти.
 +
 +
<pre>
 +
#!/usr/bin/perl
 +
# Блокируем если более 5 запросов "/" без запроса '/idx.html' или при запросе маски '/search/?str=...'
 +
my %flag_hash=();
 +
my %ua_hash=();
 +
while(<>){
 +
    my ($s_ip,undef,undef,undef,undef,$s_cmd,$s_req,undef) = split(/\s/,$_);
 +
    if ($s_req eq '/' ){
 +
        if (! defined $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;
 +
    }
 +
}
 +
foreach my $ip (keys %flag_hash){
 +
    if ($flag_hash{$ip} < 5){next;}
 +
    print "$flag_hash{$ip} $ip\n";
 +
}
 +
 +
 +
</pre>
 +
 +
/boot/loader.conf
 +
<pre>
 +
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"
 +
</pre>
 +
 +
/etc/sysctl.conf:
 +
<pre>
 +
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
 +
</pre>
 +
 +
nginx.conf (часть конфига):
 +
 +
<pre>
 +
    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;
 +
    }
 +
 
 +
</pre>
 +
 +
store_log.sh - скрипт для вывода разной системной статистики разом
 +
 +
<pre>
 +
#!/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
 +
</pre>

Версия 21:03, 14 сентября 2009

Опыт противостояния 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 можно использовать:

  cd /etc/ ; 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`

#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
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 в памяти.

#!/usr/bin/perl
# Блокируем если более 5 запросов "/" без запроса '/idx.html' или при запросе маски '/search/?str=...'
my %flag_hash=();
my %ua_hash=();
while(<>){
    my ($s_ip,undef,undef,undef,undef,$s_cmd,$s_req,undef) = split(/\s/,$_);
    if ($s_req eq '/' ){
        if (! defined $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;
    }
}
foreach my $ip (keys %flag_hash){
    if ($flag_hash{$ip} < 5){next;}
    print "$flag_hash{$ip} $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