Простой DDoS анализ через CLI

Проблема

Исходные данные: канал 10Гбит, периодическая мощная DDoS атака на один из ip в сети приводит к деградации сервиса. График DDoS атаки далее, видно, что мощность DDoS атаки в сумме с текущим трафиком превышает емкость канала.

Решение

Так как быстро расширить канал и увеличить мощность DPI не представлялось возможным, выбрали следующий путь: 1. вычисляем список IP которые подверглись DDoS 2. переводим IP в null route ( в blackhole )

Настройка

создаем директорию /home/ddos_check

устанавливаем ipfixreceiver2 на сервер, где будем собирать данные полный netflow используем следующую конфигурацию файл ipfixreceiverflow2.ini, не требуется гарантированная доставка поэтому используем UDP транспорт

[connect]
#protocol=tcp
protocol=udp
host=0.0.0.0
port=1599
[dump]
rotate_minutes=1
processcmd=/home/ddos_check/rcflowprocess %%s
dumpfiledir=/home/ddos_check/flow/
[InfoModel]
XMLElements = /etc/rcollector/xml/raw_flow.xml
[Template]
Elements = octetDeltaCount, packetDeltaCount, protocolIdentifier, ipClassOfService, sourceTransportPort, sourceIPv4Address, sourceIPv6Address, destinationTransportPort, destinationIPv4Address, destinationIPv6Address, bgpSourceAsNumber, bgpDestinationAsNumber, flowStartMilliseconds, flowEndMilliseconds, ingressInterface, egressInterface, ipVersion, session_id, host_cn, DPI_PROTOCOL, login, postNATSourceIPv4Address, postNAPTSourceTransportPort, frgmt_delta_packs, repeat_delta_pack, packet_deliver_time
[ExportModel]
Elements = session_id, octetDeltaCount, protocolIdentifier, DPI_PROTOCOL, sourceTransportPort, sourceIPv4Address : decode_unsigned, destinationTransportPort, destinationIPv4Address : decode_unsigned, bgpSourceAsNumber, bgpDestinationAsNumber, flowStartMilliseconds : decode_unsigned, flowEndMilliseconds : decode_unsigned, login, postNATSourceIPv4Address : decode_unsigned, postNAPTSourceTransportPort, packetDeltaCount, sourceIPv6Address, destinationIPv6Address
[logging]
loggers.root.level = information
loggers.root.channel = fileChannel
channels.fileChannel.class = FileChannel
channels.fileChannel.path = /var/log/ipfixreceiverflow2.log
channels.fileChannel.rotation = 1 M
channels.fileChannel.archive = timestamp
channels.fileChannel.purgeCount = 5
channels.fileChannel.formatter.class = PatternFormatter
channels.fileChannel.formatter.pattern = %Y-%m-%d %H:%M:%S.%i [%P] %p %s - %t
channels.fileChannel.formatter.times = local

сохраняем файл в /home/ddos_check

вносим в iptables разрешение для порта 1599 - UDP, запускаем ipfixreceiver.

ipfixreceiver2 --daemon --umask=000 --pidfile=/var/run/ipfixreceiver.1599.pid -f /home/ddos_check/ipfixreceiverflow2.ini
#проверяем, что порт процесс слушает
netstat -anpl | grep 1599
udp 124968 0 0.0.0.0:1599 0.0.0.0:* 21820/ipfixreceiver

создаем файл обработки потока /home/ddos_check/rcflowprocess

#!/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/bin:/home/ddos_check
export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib:/usr/local/lib
gzip $1
echo "DDoS statistics" > /home/ddos_check/lastminute.txt
date >> /home/ddos_check/lastminute.txt
echo -e "\nSessions TOP20\t ip" >> /home/ddos_check/lastminute.txt
/home/ddos_check/topcnt $1.gz >> /home/ddos_check/lastminute.txt
echo -e "\n\nSummary bytes Only Destination TOP20\nsize(Mb) \t ip" >> /home/ddos_check/lastminute.txt
/home/ddos_check/topsize $1.gz 2 | sort -n -r| head -20 | awk '{print $1/1024/1024 " " $2'} >> /home/ddos_checklastminute.txt
echo -e "\n\nSummary bytes Only src IP TOP20\nsize(Mb) \t ip" >> /home/ddos_check/lastminute.txt
/home/ddos_check/topsize $1.gz 1 | sort -n -r| head -20 | awk '{print $1/1024/1024 " " $2'} >> /home/ddos_check/lastminute.txt
echo -e "\n\nSummary bytes by src+dsc TOP20\nsize(Mb)\tip" >> /home/ddos_check/lastminute.txt
/home/ddos_check/topsize $1.gz 0 | sort -n -r| head -20 | awk '{print $1/1024/1024 " " $2'} >> /home/ddos_check/lastminute.txt
mv -f /home/ddos_check/lastminute.txt /var/www/html/ddos_check/lastminute.txt
chown apache /var/www/html/ddos_check/lastminute.txt
chmod a+w /var/www/html/ddos_check/lastminute.txt
chcon -v --type=httpd_sys_content_t /var/www/html/ddos_check/lastminute.txt

не забываем сделать chmod a+x /home/ddos_check/rcflowprocess

создаем скрипт расчета к-ва сессий top-20 /home/ddos_check/topcnt

function getipv4() {
s=`echo "obase=16; " $1 | bc | sed 's/../0x& /g'`
ip=`printf '%d.%d.%d.%d' $s`
echo -n $ip
}
zcat $1 | awk -F '\t' '{print $6 "\n" $8}' | sort | uniq -c -d | sort -n -r > tmp$$$
head -20 tmp$$$ > tmp2$$$
echo -e "ip\t\thits"
while read p; do
cnt=`echo $p | awk '{print $1}'`
ipd=`echo $p | awk '{print $2}'`
size=`/home/volja/cntsums $1 $ipd | awk '{print $2/1024/1024}'`
getipv4 $ipd; echo -n -e "\t" $cnt; echo -e "\t s=" $size "(Mb)\t(" $ipd ")"
done < tmp2$$$
rm -f tmp$$$ tmp2$$$

не забываем сделать chmod a+x /home/ddos_check/topcnt

устанавливаем bc

yum -y install bc

создаем скрипт /home/ddos_check/cntsums

zcat $1 | grep $2 | awk '{sum += $2; sum2 += $3} END {print "octets= " sum "\tpackets= " sum2}'

не забываем сделать chmod a+x /home/ddos_check/cntsums

создаем скрипт ТОП-20 по максимальному объему трафика /home/ddos_check/topsize

#!/usr/bin/python
# usage:
# by source ip
#./topsize arch/attak1/url_05102017_152100.dump.gz 1 | sort -n -r| head -20 | awk '{print $1/1024/1024 " " $2'}
# by destination ip
#./topsize arch/attak1/url_05102017_152100.dump.gz 2 | sort -n -r| head -20 | awk '{print $1/1024/1024 " " $2'}
#
import sys, os, logging, ConfigParser, gzip
def main():
delim='\t'
accumulater={}
for line in openinfile(sys.argv[1]):
acc=long(0)
fields = line.rstrip('\n').split(delim)
if(sys.argv[2]=="0" or sys.argv[2]=="1"):
# by src
try:
acc=accumulater[fields[5]]
accumulater[fields[5]]=acc+long(fields[1])
except KeyError:
accumulater[fields[5]]=long(fields[1])
if(sys.argv[2]=="0" or sys.argv[2]=="2"):
#by dsc
try:
acc=accumulater[fields[7]]
accumulater[fields[7]]=acc+long(fields[1])
except KeyError:
accumulater[fields[7]]=long(fields[1])
for key, value in accumulater.iteritems():
print str(value)+' '+ipv4str(long(key))
# open input file
def openinfile(filename):
if filename is None:
inf = sys.stdin
logging.debug("input file: stdin")
else:
if ".gz" in filename:
inf = gzip.open(filename, "rb")
else:
inf = open(filename, "rb")
logging.debug("input file: " + filename)
return inf
def ipv4str(ipv4):
return str((ipv4 >> 24) & 0xFF) + '.' + str((ipv4 >> 16) & 0xFF) + '.' + str((ipv4 >> 8) & 0xFF) + '.' + str((ipv4 & 0xFF))
if __name__ == "__main__":
main()

не забываем сделать chmod a+x /home/ddos_check/topsize

добавляем строку в /var/spool/cron/root что бы через сутки данные flow удалялись

15 4 * * * /bin/find /home/ddos_check/flow/ -name url_\*.dump.gz -cmin +1440 -delete > /dev/null 2>&1

устанавливаем параметры на DPI

netflow=8
netflow_full_collector_type=1
netflow_dev=eth2
netflow_timeout=10
#!!!здесь укажите ip адрес вашего приемника
netflow_full_collector=127.0.0.1:1599
netflow_passive_timeout=20
netflow_active_timeout=60

данная настройка требует рестарта

Работа со скриптами

После перезагрузки fastdpi если все сконфигурировали правильно ipfixreceiver начнет принимать данные в директорию /home/ddos_check/flow, после появления *.gz файлов (1 раз в минуту интервал ротации был установлен в приемнике) проводим проверку скриптов.

cd /home/ddos_check
./topcnt flow/url_05102017_151800.dump.gz
ip hits
77.XXX.XX.64 144889 s= 5379.99 (Mb) ( 1299787840 )
77.88.8.8 14051 s= 2.26185 (Mb) ( 1297614856 )
128.128.128.8 1642 s= 0.401568 (Mb) ( 134744072 )
77.88.8.1 1578 s= 0.359544 (Mb) ( 1297614849 )
...
77.XXX.XX.208 468 s= 1.45243 (Mb) ( 1299785168 )

видим 1 шт. ip в топе с очень большим отрывом, этот адрес ddos'ят из вне забивая канал. Проверяем top по размеру по всем src и dsc IP: ./topsize arch/attak1/url_05102017_151800.dump.gz 0 | sort -n -r| head -20 | awk '{print $1/1024/1024 " " $2'} 5380.01 77.XXX.XX.64 165.881 81.XXX.XXX.79 … 27.2184 74.XXX.XXX.27 видим что в текущую минуту данный IP получил и отдал 5.4 Гбайт трафика, следующий только 165 Мбайт. Таким образом подтвердилось предположение о DDoS атаке на IP=77.XXX.XX.64.
Данный адрес отправляем в blackhole, так как мощности канала не хватает, т.е. блокировать можно только у вышестоящего провайдера с помощью null route.

Настройка httpd

Для оперативного доступа при наличии http на сервере где происходит прием IPFX можно прописать в конфигурации /etc/httpd/conf/httpd.conf:

<Directory "/var/www/html/ddos_check">
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
Alias /ddos_check/ "/var/www/html/ddos_check/"

Желательно еще добавить ограничение доступа, в данном примере не рассматривается.
Соответственно после рестарта httpd можно получить страницу по ссылке

http://<your_ip_http>/ddos_check/lastminute.txt

Что в остатке?

Интересно было понять м.б. можно заблокировать только ip извне? или их там пара десятков?
создаем скрипт для подсчета мощности атаки /home/ddos_check/topip:

#!/bin/bash
function getipv4() {
s=`echo "obase=16; " $1 | bc | sed 's/../0x& /g'`
ip=`printf '%d.%d.%d.%d' $s`
echo -n $ip
}
zcat $1 | grep $2 | awk -F '\t' '{print $8 "\n" $6}' | grep -v $2 | sort | uniq -c -d | sort -n -r > tmp$$$
echo -e "ip\t\thits"
echo -n "unique ip="; wc -l tmp$$$ | awk '{print $1}'
head -20 tmp$$$ > top20$$$
while read p; do
cnt=`echo $p | awk '{print $1}'`
ipd=`echo $p | awk '{print $2}'`
getipv4 $ipd; echo -e "\t" $cnt
done <top20$$$
rm -f tmp$$$ top20$$$

не забывает сделать chmod a+x /home/ddos_check/topip запускаем, берем в качестве 2-го параметра десятичное представление IP из () в TOP-20 сессий:

./topip 'arch/attak1/url_05102017_15*' 1299787840
ip hits
unique ip=58261
202.92.200.6 2626
110.76.131.6 2546
119.235.28.59 2150
38.70.202.194 1874
177.38.144.14 1830
138.204.18.18 1542
212.119.180.222 1452
88.220.134.2 1432
200.186.13.86 1389
...

как видно из таблицы в атаке участвуют 58 тысяч адресов.

Ссылки по теме

Защита от DDOS атак средствами BGP