LINUX.ORG.RU

Защита от DDoS и флуда (iptables)

 , , , ,


9

6

Поделюсь своим опытом борьбы с DDoS флудом. Защищать будем операционную систему openSUSE Linux с помощью правил iptables.

Для защиты настроим систему и создадим скрипт, который будет отслеживать флуд соединениями на открытые порты.

Сначала о настройке системы. Моё содержимое файла /etc/sysctl.conf:

# IPv6 здесь отключен, пинг отключен
kernel.sysrq = 0
net.ipv4.ip_forward = 0 #это если компьютер не используется как шлюз
net.ipv4.tcp_syncookies = 1
net.ipv6.conf.all.forwarding = 0
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1 #игнорируем broadcasts пакеты
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.conf.all.send_redirects = 0 #это если компьютер не используется как маршрутизатор
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 4294967295
kernel.shmall = 268435456
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.icmp_echo_ignore_all=1 #icmp полностью игнорируется
net.ipv4.tcp_max_syn_backlog=2048
net.ipv4.tcp_synack_retries=1
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_fin_timeout = 20
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0net.ipv4.tcp_timestamps = 0

Теперь о правилах iptables.

Создайте файл /etc/init.d/iptables_myrules и сделайте его исполняемым: chmod +x /etc/init.d/iptables_myrules. Его содержимое:

#!/bin/bash
# PhazaSoft iptables rules
# Специально для пользователей SteelLinux
# http://steellinux.do.am

### BEGIN INIT INFO
# Provides: iptables_myrules
# Required-Start: $network
# Should-Start: $network
# Required-Stop:
# Should-Stop:
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
# Short-Description: iptables user's rules
# Description: iptables user's rules
### END INIT INFO


########### НАСТРОЙКИ ########### ###>>>

# названия интерфейсов, которые будем защищать (через пробел)
PROTECTZONE="dsl0"
# названия интерфейсов, где защита не нужна (через пробел)
FREEZONE="lo eth0"

# перечень портов в кавычках через запятые пробелов, которые нужно разрешить (отдельно TCP и UDP)
TCP_PORTS="80,411,1209"
UDP_PORTS=""
# перечень портов только для разрешённых диапазонов
TCP_PORTS_PRIV=""
UDP_PORTS_PRIV=""
# разрешённые диапазоны для портов в TCP_PORTS_PRIV и UDP_PORTS_PRIV (если они заданы)
PRIV_RANGES="
0.0.0.0-0.0.0.0
127.0.0.0-127.255.255.255
192.168.0.0-192.168.255.255
172.20.0.0-172.20.255.255
"

# активировать правило connlimit для одновременных активных соединений с одного айпи? Указывается число соединений или 0-выключено.
IS_CONNLIMIT="10"	# 0-выкл
CONNLIMIT_MASK="32"	# маска для подсетей адресов одновременных активных соединений (32 - каждый IP индивидуален)

# правило ограничения количества соединений за заданное время с помощью recent
IS_RECENT="1"		# 1-вкл, 0-выкл
RECENT_SECONDS="60"	# период, за который не должно быть превышения количества соединений (в секундах)
RECENT_HITCOUNT="12"	# количество соединений за заданный период времени

# настройка ограничений количества соединений с помощью hashlimit
HASHLIMIT_UPTO="12/min"	# количество соединений в единицу времени
HASHLIMIT_BURST="6"	# пик количества разовой доставки соединений
HASHLIMIT_MODE="srcip"	# режим хеширования
HASHLIMIT_EXPIRE="60000"	# время жизни записи в хэш-таблице (в миллисекундах)

# разрешить GRE протокол (например, для VPN)
IS_GRE="1"		# 0-выкл

# udp broadcast трафик (в том чиcле IP-TV и прочее)
IS_BROADCAST="0"		# 1-вкл, 0-выкл

# icmp echo пакеты (пинги)
IS_ICMP_ECHO="0"		# 1-вкл, 0-выкл

# чёрный список IP-адресов, которые будут блокироваться
BLACKLIST_IP="
89.222.164.212
213.88.49.71
"

# чёрный список диапазонов IP-адресов, которые будут блокироваться
BLACKLIST_RANGES="
72.36.64.0-72.36.127.255
130.126.0.0-130.126.255.255
192.17.0.0-192.17.255.255
"

# чёрный список кодов стран, которые будут блокироваться (модуль geoip)
BLACKLIST_COUNTRIES="CN,KR,JP" # через запятую без пробелов

IPT=/usr/sbin/iptables	# путь к iptables

################################# ###<<<
_VERSION="1.1"


#преобразуем список портов в секции, не превышающие 15 портов на секцию (ограничение в multiport)
TCP_PORTS=$(echo "$TCP_PORTS" | sed -E 's/\s//g')
TCP_PORTS_PARSED=""
while [ $(echo $TCP_PORTS | sed -E 's/,/ /g' | wc -w) -gt "15" ]
do
  TCP_PORTS_PARSED="$TCP_PORTS_PARSED $(echo $TCP_PORTS | sed -E 's/([0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*),.*/\1/')"
  TCP_PORTS=$(echo $TCP_PORTS | sed -E 's/[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,(.*)/\1/')
done
TCP_PORTS_PARSED="$TCP_PORTS_PARSED $TCP_PORTS"

UDP_PORTS=$(echo "$UDP_PORTS" | sed -E 's/\s//g')
UDP_PORTS_PARSED=""
while [ $(echo $UDP_PORTS | sed -E 's/,/ /g' | wc -w) -gt "15" ]
do
  UDP_PORTS_PARSED="$UDP_PORTS_PARSED $(echo $UDP_PORTS | sed -E 's/([0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*),.*/\1/')"
  UDP_PORTS=$(echo $UDP_PORTS | sed -E 's/[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,(.*)/\1/')
done
UDP_PORTS_PARSED="$UDP_PORTS_PARSED $UDP_PORTS"

TCP_PORTS_PRIV=$(echo "$TCP_PORTS_PRIV" | sed -E 's/\s//g')
TCP_PORTS_PRIV_PARSED=""
while [ $(echo $TCP_PORTS_PRIV | sed -E 's/,/ /g' | wc -w) -gt "15" ]
do
  TCP_PORTS_PRIV_PARSED="$TCP_PORTS_PRIV_PARSED $(echo $TCP_PORTS_PRIV | sed -E 's/([0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*),.*/\1/')"
  TCP_PORTS_PRIV=$(echo $TCP_PORTS_PRIV | sed -E 's/[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,(.*)/\1/')
done
TCP_PORTS_PRIV_PARSED="$TCP_PORTS_PRIV_PARSED $TCP_PORTS_PRIV"

UDP_PORTS_PRIV=$(echo "$UDP_PORTS_PRIV" | sed -E 's/\s//g')
UDP_PORTS_PRIV_PARSED=""
while [ $(echo $UDP_PORTS_PRIV | sed -E 's/,/ /g' | wc -w) -gt "15" ]
do
  UDP_PORTS_PRIV_PARSED="$UDP_PORTS_PRIV_PARSED $(echo $UDP_PORTS_PRIV | sed -E 's/([0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*),.*/\1/')"
  UDP_PORTS_PRIV=$(echo $UDP_PORTS_PRIV | sed -E 's/[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*,(.*)/\1/')
done
UDP_PORTS_PRIV_PARSED="$UDP_PORTS_PRIV_PARSED $UDP_PORTS_PRIV"


do_rules() {
  echo 1 > /proc/sys/net/ipv4/ip_forward
  echo 0 > /proc/sys/net/ipv4/ip_forward   #если наш компьютер не используется как шлюз
  for i in /proc/sys/net/ipv4/conf/*/send_redirects; do echo 0 > $i; done   #если наш компьютер не используется как маршрутизатор
  for i in /proc/sys/net/ipv4/conf/*/rp_filter; do echo 1 > $i; done
  echo 1 > /proc/sys/net/ipv4/tcp_syncookies
  echo 1 > /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses
  for i in /proc/sys/net/ipv4/conf/*/accept_redirects; do echo 0 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/accept_source_route; do echo 0 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/mc_forwarding; do echo 0 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/proxy_arp; do echo 0 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/secure_redirects; do echo 1 > $i; done
  for i in /proc/sys/net/ipv4/conf/*/bootp_relay; do echo 0 > $i; done
  
  # Настраиваем политики по умолчанию
  $IPT -P INPUT DROP   #политика по умолчанию для входящих - запрет
  $IPT -P OUTPUT ACCEPT
  $IPT -P FORWARD ACCEPT

  # удаляем все имеющиеся правила
  $IPT -F
  $IPT -t nat -F
  $IPT -t mangle -F

  $IPT -X
  $IPT -t nat -X
  $IPT -t mangle -X
  
  $IPT -Z
  $IPT -t nat -Z
  $IPT -t mangle -Z
  
  for interface in $FREEZONE
  do
    $IPT -A INPUT -i $interface -j ACCEPT
    $IPT -A OUTPUT -o $interface -j ACCEPT
  done #разрешаем активность на незащищаемых интерфейсах
  
  $IPT -A INPUT -m conntrack --ctstate INVALID -j DROP
  $IPT -A OUTPUT -m conntrack --ctstate INVALID -j DROP
  $IPT -A FORWARD -m conntrack --ctstate INVALID -j DROP
  
  $IPT -A INPUT -p tcp -m conntrack --ctstate NEW --tcp-flags ALL ALL -j DROP
  $IPT -A INPUT -p tcp -m conntrack --ctstate NEW --tcp-flags ALL NONE -j DROP
  $IPT -A INPUT -p tcp -m conntrack --ctstate NEW ! --syn -j DROP
  $IPT -A INPUT -m conntrack --ctstate NEW,INVALID -p tcp --tcp-flags SYN,ACK SYN,ACK -j REJECT --reject-with tcp-reset
  
  $IPT -A INPUT -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
  $IPT -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
  $IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
  
  #блокируем некоторые айпи и страны
  for ip in $BLACKLIST_IP
  do
    $IPT -I INPUT -s $ip -j DROP
  done
  for range in $BLACKLIST_RANGES
  do
    $IPT -I INPUT -m iprange --src-range $range -j DROP
  done
  for countries in $BLACKLIST_COUNTRIES
  do
    $IPT -I INPUT -m geoip --src-cc $countries -j DROP
  done
  
  
  if [ $IS_RECENT -gt "0" ]
  then
    for ports in $TCP_PORTS_PARSED
    do
      $IPT -A INPUT -p tcp -m tcp -m multiport --dports $ports -m conntrack --ctstate NEW -m recent --set --name ddos_block_conn_tcp
      $IPT -A INPUT -p tcp -m tcp -m multiport --dports $ports -m conntrack --ctstate NEW -m recent --update --seconds $RECENT_SECONDS --hitcount $RECENT_HITCOUNT --name ddos_block_conn_tcp -j DROP
    done
    
    for ports in $UDP_PORTS_PARSED
    do
      $IPT -A INPUT -p udp -m udp -m multiport --dports $ports -m conntrack --ctstate NEW -m recent --set --name ddos_block_conn_udp
      $IPT -A INPUT -p udp -m udp -m multiport --dports $ports -m conntrack --ctstate NEW -m recent --update --seconds $RECENT_SECONDS --hitcount $RECENT_HITCOUNT --name ddos_block_conn_udp -j DROP
    done
    
    for range in $PRIV_RANGES
    do
      for ports in $TCP_PORTS_PRIV_PARSED
      do
        $IPT -A INPUT -p tcp -m tcp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m recent --set --name ddos_block_conn_tcp
        $IPT -A INPUT -p tcp -m tcp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m recent --update --seconds $RECENT_SECONDS --hitcount $RECENT_HITCOUNT --name ddos_block_conn_tcp -j DROP
      done
      
      for ports in $UDP_PORTS_PRIV_PARSED
      do
        $IPT -A INPUT -p udp -m udp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m recent --set --name ddos_block_conn_udp
        $IPT -A INPUT -p udp -m udp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m recent --update --seconds $RECENT_SECONDS --hitcount $RECENT_HITCOUNT --name ddos_block_conn_udp -j DROP
      done
    done
  fi #ограничение количества соединений за заданное время
  
  for interface in $PROTECTZONE
  do
    for ports in $TCP_PORTS_PARSED
    do
      if [ $IS_CONNLIMIT -gt "0" ]
      then
        $IPT -A INPUT -i $interface -p tcp -m multiport --dports $ports -m connlimit --connlimit-above $IS_CONNLIMIT --connlimit-mask $CONNLIMIT_MASK -j DROP
      fi
      $IPT -A INPUT -i $interface -p tcp -m multiport --dports $ports -m conntrack --ctstate NEW -m hashlimit --hashlimit-upto $HASHLIMIT_UPTO --hashlimit-burst $HASHLIMIT_BURST --hashlimit-mode $HASHLIMIT_MODE --hashlimit-htable-expire $HASHLIMIT_EXPIRE --hashlimit-name ddos_block_tcp -j ACCEPT
    done
    
    for ports in $UDP_PORTS_PARSED
    do
      if [ $IS_CONNLIMIT -gt "0" ]
      then
        $IPT -A INPUT -i $interface -p udp -m multiport --dports $ports -m connlimit --connlimit-above $IS_CONNLIMIT --connlimit-mask $CONNLIMIT_MASK -j DROP
      fi
      $IPT -A INPUT -i $interface -p udp -m multiport --dports $ports -m conntrack --ctstate NEW -m hashlimit --hashlimit-upto $HASHLIMIT_UPTO --hashlimit-burst $HASHLIMIT_BURST --hashlimit-mode $HASHLIMIT_MODE --hashlimit-htable-expire $HASHLIMIT_EXPIRE --hashlimit-name ddos_block_udp -j ACCEPT
    done
    
    for range in $PRIV_RANGES
    do
      for ports in $TCP_PORTS_PRIV_PARSED
      do
        if [ $IS_CONNLIMIT -gt "0" ]
        then
          $IPT -A INPUT -i $interface -p tcp -m multiport --dports $ports -m iprange --src-range $range -m connlimit --connlimit-above $IS_CONNLIMIT --connlimit-mask $CONNLIMIT_MASK -j DROP
        fi
        $IPT -A INPUT -i $interface -p tcp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m hashlimit --hashlimit-upto $HASHLIMIT_UPTO --hashlimit-burst $HASHLIMIT_BURST --hashlimit-mode $HASHLIMIT_MODE --hashlimit-htable-expire $HASHLIMIT_EXPIRE --hashlimit-name ddos_block_tcp -j ACCEPT
      done
      
      for ports in $UDP_PORTS_PRIV_PARSED
      do
        if [ $IS_CONNLIMIT -gt "0" ]
        then
          $IPT -A INPUT -i $interface -p udp -m multiport --dports $ports -m iprange --src-range $range -m connlimit --connlimit-above $IS_CONNLIMIT --connlimit-mask $CONNLIMIT_MASK -j DROP
        fi
        $IPT -A INPUT -i $interface -p udp -m multiport --dports $ports -m iprange --src-range $range -m conntrack --ctstate NEW -m hashlimit --hashlimit-upto $HASHLIMIT_UPTO --hashlimit-burst $HASHLIMIT_BURST --hashlimit-mode $HASHLIMIT_MODE --hashlimit-htable-expire $HASHLIMIT_EXPIRE --hashlimit-name ddos_block_udp -j ACCEPT
      done
    done
  done # открываем входящие порты на защищаемых интерфейсах
  
  if [ $IS_BROADCAST -eq "0" ]
  then
    echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
    $IPT -A INPUT -p udp -m pkttype --pkt-type broadcast -j DROP
  else
    echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
  fi #запрещаем broadcast пакеты
  
  if [ $IS_ICMP_ECHO -eq "0" ]
  then
    echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
    #$IPT -A INPUT -p icmp --icmp-type echo-request -j DROP
    #$IPT -A INPUT -p icmp -j DROP
    #$IPT -A OUTPUT -p icmp -j ACCEPT
  else
    echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all
  fi # запрещаем пинги
  
  
  for interface in $PROTECTZONE
  do
    $IPT -A INPUT -i $interface -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
    $IPT -A OUTPUT -o $interface -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
    if [ $IS_GRE -gt "0" ]
    then
      $IPT -A INPUT -i $interface -p gre -j ACCEPT
      $IPT -A OUTPUT -o $interface -p gre -j ACCEPT
    fi
  done #разрешаем входящую активность уже установленных соединений и создание исходящих соединений на защищаемых интерфейсах

}



case $1 in
'list')
  $IPT -L -n -v -x
  ;;
  
'clean')
  $IPT -P INPUT ACCEPT
  $IPT -P OUTPUT ACCEPT
  $IPT -P FORWARD ACCEPT
  $IPT -F
  $IPT -L -n -v -x
  ;;
  
'help')
  echo "PhazaSoft antiddos v.$_VERSION"
  echo 'iptables_myrules [start|clean|list|help]'
  ;;
  
*)
  do_rules
  ;;
esac

Здесь заданы непосредственно правила iptables.

Рассмотрим настройки:

PROTECTZONE - в этой константе перечисляются через пробел интерфейсы, которые будут защищаться правилами (внешняя зона). Обычно это один интерфейс, через который осуществляется выход в интернет. В данном примере это интерфейс dsl0. Вы должны задать свой интерфейс.
FREEZONE - здесь перечисляются через пробел интерфейсы внутренней зоны, на которой разрешена любая активность (локальные интерфейсы). Интерфейс lo должен обязательно быть здесь. Если у Вас лишь один cетевой интерфейс (например, eth0), через который осуществляется выход в интернет, то он должен быть указан во внешней зоне, а интефейс lo во внутренней.
TCP_PORTS - перечень TCP портов через запятую без пробелов, которые нужно открыть (на которых у нас работают те или иные сервисы, принимающие входящие соединения из внешней зоны).
UDP_PORTS - перечень UDP портов через запятую без пробелов, которые нужно открыть.
TCP_PORTS_PRIV и UDP_PORTS_PRIV - аналогичны константам TCP_PORTS и UDP_PORTS соответственно, только к перечисленным здесь портам будут разрешены подключения лишь с определённых диапазонов IP-адресов, перечисленных в константе PRIV_RANGES (приватные диапазоны).
PRIV_RANGES - перечень приватных диапазонов IP-адресов (начальный-конечный, без пробелов через дефиз), по одному диапазону на каждой строчке. Перечисленные здесь адреса допускаются для соединения с портами, перечисленными в константах TCP_PORTS_PRIV и UDP_PORTS_PRIV. Чтобы задать лишь один IP-адрес, просто укажите одинковые начальный и конечный адреса диапазона.
IS_CONNLIMIT - задаётся разрешённое число оновременных соединений с одного IP-адреса (модуль connlimit). Если задать значение '0', то правило будет отключено.
CONNLIMIT_MASK - маска для проверки одновременных соединений модуля connlimit.
IS_RECENT - активация модуля recent, который ограничивает число соединений с одного адреса за определённый период времени. 1 - включено, 0 - выключено.
RECENT_SECONDS - период в секундах, за который не должно быть превышения лимита количества соединений за заданный интервал времени с одного IP-адреса.
RECENT_HITCOUNT - количество соединений за заданный период времени для модуля recent.
HASHLIMIT_UPTO - количество соединений в единицу времени для модуля hashlimit. Временной интервал может быть: /sec, /min, /hour, /day. При превышении данного лимита пакет будет заблокирован.
HASHLIMIT_BURST - пик количества разовой доставки соединений для модуля hashlimit.
HASHLIMIT_MODE - режим хеширования для модуля hashlimit. Варианты могут быть: dstip, srcip, dstport, srcport (несколько разделяютя запятыми без пробелов).
HASHLIMIT_EXPIRE - время жизни записи в хэш-таблице для модуля hashlimit.
IS_GRE - разрешение протокола GRE (1 - включено, 0 - выключено).
IS_BROADCAST - разрешение broadcast трафика (1 - включено, 0 - выключено).
IS_ICMP_ECHO - icmp echo пакеты (1 - включено, 0 - выключено).
BLACKLIST_IP - здесь можно указать перечень IP-адресов, которые будут блокироваться (по одному на каждой строчке).
BLACKLIST_RANGES - здесь можно указать перечень диапазонов IP-адресов, которые будут блокироваться (по одному на каждой строчке, через дефиз без пробелов).
BLACKLIST_COUNTRIES - перечень кодов стран, которые будут блокироваться, через запятую без пробелов (модуль geoip).

Если система используется как шлюз или маршрутизатор, то нужно разрешить ip_forward и send_redirects.

При запуске скрипта без параметров все правила перезадаются. При запуске с параметром clean правила обнуляются. Параметр list выводит текущий список правил и статистику срабатываний.

Для добавления скрипта в автозапуск, выполните команду: chkconfig -a iptables_myrules. Для удаления скрипта из автозапуска выполните команду: chkconfig iptables_myrules off.

Теперь опишу дополнительный скрипт ddos_block.lua, который следит за входящими соединениями на заданные порты (или группы портов) и блокирует IP-адреса, с которых превышен лимит одновременного числа установленных соединений. Этот лимит задаётся отдельно для каждого порта или группы портов. Вот код скрипта:

#!/usr/bin/lua
--[[
  PhazaSoft DDoS antiflood
  Специально для пользователей SteelLinux
  http://steellinux.do.am
]]


-- ################## НАСТРОЙКА ################## >>>

local ports_protect={
	[{22}]=2,
	[{80,413}]=15,
	[{411,1209}]=3,
} -- защищаемые порты (группа задаётся через запятую) и разрешённое количество одновременных соединений для данной группы портов с одного IP

local ranges_allow={
	[{"0.0.0.0","0.0.0.0"}]="all", --local
	[{"10.0.0.0","10.255.255.255"}]="all",
	[{"127.0.0.0","127.255.255.255"}]="all",
	[{"172.16.0.0","172.31.255.255"}]="all",
	[{"192.168.0.0","192.168.255.255"}]="all",
} -- разрешённые диапазоны (white list) и порты для них через запятую (all означает все порты)

local time_ban=60*30 --время блокировки IP-адреса (в секундах)
local drop_allow=200 --разрешённое количество попыток соединения во время блокировки (при превышении блокировка продлевается)
local scan_period=10 --период между сканированиями (в секундах)
local log_folder="/var/log/ddos_block/" --папка для логов (в конце должен быть "/") (пустая строка "" означает отключение логов)
local filter_command="/bin/netstat -utan | egrep '^(tcp|udp)\\s+[0-9]+\\s+[0-9]+\\s+[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:(%PORTS)' " --шаблон команды для получения списка текущих соединений с защищаемыми портами (шаблон %PORTS заменится на защищаемые порты)
local ban_command="/usr/sbin/iptables -I INPUT -s %IP -j DROP " --шаблон команды блокировки IP-адреса
local unban_command="/usr/sbin/iptables -D INPUT -s %IP -j DROP " --шаблон команды удаления блокировки IP-адреса
local stat_command="/usr/sbin/iptables -L -n -v -x | grep 'DROP       all  \\-\\-  \\*      \\*' | grep -v 'all  \\-\\-  \\*      \\*       0.0.0.0/0' " --команда для получения статистики о заблокированных IP-адресах

-- ############################################## <<<
local VERSION="1.1"




local res,err=io.popen("whoami")
if res then
	local result=res:read("*a") or "" 
	res:close()
	if result:gsub("%s","")~="root" then
		print("ERR", "Run this program as root! Exiting.")
		return
	end
else
	print("ERR:popen:whoami", os.date(), tostring(err))
	return
end --нам нужны права рута

local function IPToLong(str)
	local d1,d2,d3,d4=str:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")
	if not d1 or not d2 or not d3 or not d4 then return end
	d1=d1*1000000000
	d2=d2*1000000
	d3=d3*1000
	d4=d4+0
	if d1<=255000000000 and d2<=255000000 and d3<=255000 and d4<=255 then return d1+d2+d3+d4 end
end --преобразование строки айпи в число
local function LongToIP(num)
	local d1,_=math.modf(num/1000000000)
	num=num-(d1*1000000000)
	local d2,_=math.modf(num/1000000)
	num=num-(d2*1000000)
	local d3,_=math.modf(num/1000)
	local d4=num-(d3*1000)
	return d1.."."..d2.."."..d3.."."..d4
end --преобразование числа в строку айпи

local function FilterIPinRanges(tab)
	for ip in pairs(tab) do
		local lip=IPToLong(ip) or (print("ERR:IPToLong", os.date(), tostring(ip)))()
		for i in pairs(ranges_allow) do
			if lip>=i[1] and lip<=i[2] then
				if not next(ranges_allow[i]) then
					tab[ip]=nil
				else
					for p in pairs(tab[ip]) do
						if ranges_allow[i][p..""] then tab[ip][p]=nil end
					end
					if not next(tab[ip]) then tab[ip]=nil end
				end --разрешены все порты или некоторые?
				break
			end --ip в белом листе
		end
	end
	return tab
end --сброс статистики соединений с разешёнными портами для ip из белого листа

local ip_banned={} --заблокированные ip
local ip_stat={} --текущая статистика

res=""
for i in pairs(ports_protect) do
	for j=1,#i do res=res..i[j].." |" end
end --составляем команду фильтра защищаемых портов
res,_=res:gsub("|$","")
filter_command,_=filter_command:gsub("%%PORTS",res)

for i in pairs(ranges_allow) do
	res=ranges_allow[i]
	ranges_allow[i]={}
	for p in res:gmatch("(%d+)") do ranges_allow[i][p]=1 end
	i[1]=IPToLong(i[1])
	i[2]=IPToLong(i[2])
end --преобразование диапазонов ip в числа и портов в таблицы

if log_folder~="" then
	os.execute("mkdir -p "..log_folder.."banned")
	os.execute("mkdir -p "..log_folder.."unbanned")
end

print("Protected ports:")
res=""
for i in pairs(ports_protect) do
	for j=1,#i do res=res..i[j].."," end
	res,_=res:gsub(",$","")
	print("", res, "("..ports_protect[i].." conn. allow)")
	res=""
end
print("Ban time:", time_ban.." sec.")
print("Connection attempts count allowed while banned:", drop_allow)
print("Period between scans:", scan_period.." sec.")
print("Logs folder:", (log_folder=="" and "disabled" or log_folder))
print("START", os.date(), "v."..VERSION)

while true do

res,err=io.popen(stat_command)
if res then
	local result=res:read("*a") or ""
	res:close()
	local tab={}
	for c,ip in result:gmatch("(%d+)%s+%d+%s+DROP%s+all%s+%-%-%s+%*%s+%*%s+(%d+%.%d+%.%d+%.%d+)") do
		tab[ip]=c+0
	end
	for ip,c in pairs(tab) do
		if ip_banned[ip] then
			local c0,t,d=(ip_banned[ip]):match("^(%d+)%s+(%d+)%s+(.+)")
			if os.time()-t > time_ban then
				if (c+0<ip_stat[ip] and c+0<=drop_allow) or (c+0>=ip_stat[ip] and c-ip_stat[ip]<=drop_allow) then
					local s,_=unban_command:gsub("%%IP",ip)
					res,err=io.popen(s)
					if res then
						res:close()
						print("UNBAN", ip, c, os.date())
						if log_folder~="" then
							os.execute("mv -f "..log_folder.."banned/"..ip.." "..log_folder.."unbanned/ 2> /dev/null")
						end
						ip_banned[ip]=nil
						ip_stat[ip]=nil
					else
						print("ERR:popen:unban_command", os.date(), tostring(err))
					end --пробуем разблокировать
				else
					print("PROLONG", ip, c, os.date())
					if log_folder~="" then
						os.execute("echo '>>> "..os.date().."\t"..c.." (PROLONG)' >> "..log_folder.."banned/"..ip.." ; /bin/netstat -utanp | grep "..ip.." >> "..log_folder.."banned/"..ip)
					end
					ip_banned[ip]=c0.."\t"..os.time().."\t"..d
					ip_stat[ip]=c+0
				end --не превышает ли количество попыток соединения разрешённое число во время блокировки? Продлеваем бан или разблокируем?
			end --возможно, пришло время разблокировать?
		end --проверяем список блокировок на возможность разблокировки
	end --сканируем текущие соединения
	
	for ip in pairs(ip_banned) do
		if tab[ip]==nil then
			print("ZOMBIE", ip, "", os.date())
			if log_folder~="" then
				os.execute("mv -f "..log_folder.."banned/"..ip.." "..log_folder.."unbanned/ 2> /dev/null")
			end
			ip_banned[ip]=nil
			ip_stat[ip]=nil
		end
	end --удаление из базы банов-призраков
	tab=nil
else
	print("ERR:popen:stat_command", os.date(), tostring(err))
end --проверка статистики блокировок

res,err=io.popen(filter_command)
if res then
	local result=res:read("*a") or ""
	res:close()
	local tab={}
	for c,ip in result:gmatch("%w+%s+%d+%s+%d+%s+[%d%.]+:(%d+)%s+(%d+%.%d+%.%d+%.%d+)") do
		if not tab[ip] then tab[ip]={} end
		tab[ip][c]=(tab[ip][c] or 0)+1
	end --составление списка текущих соединений с сортировкой по портам
	tab=FilterIPinRanges(tab) --фильтруем белый список диапазонов ip
	for ip in pairs(tab) do
		for i in pairs(ports_protect) do
			local gres=0
			for j=1,#i do
				gres=gres+(tab[ip][i[j]..""] or 0)
			end
			if gres>ports_protect[i] then
				if ip_banned[ip]==nil then
					local s,_=ban_command:gsub("%%IP",ip)
					res,err=io.popen(s)
					if res then
						res:close()
						s,_=unban_command:gsub("%%IP",ip)
						print("BAN", ip, gres, os.date(), "(for unban: "..s..")")
						if log_folder~="" then
							os.execute("mv -f "..log_folder.."unbanned/"..ip.." "..log_folder.."banned/ 2> /dev/null ; echo '>>> "..os.date().."\t"..gres.."' >> "..log_folder.."banned/"..ip.." ; /bin/netstat -utanp | grep "..ip.." >> "..log_folder.."banned/"..ip)
						end
						ip_banned[ip]=gres.."\t"..os.time().."\t"..os.date()
						ip_stat[ip]=0
						--os.execute("sleep 1")
					else
						print("ERR:popen:ban_command", os.date(), tostring(err))
					end --пытаемся заблокировать
				else
					local c0,t,d=(ip_banned[ip]):match("^(%d+)%s+(%d+)%s+(.+)")
					ip_banned[ip]=(c0+0>gres) and (c0.."\t"..os.time().."\t"..d) or (gres.."\t"..os.time().."\t"..d) --просто обновляем данные об ip
				end --новый ip или уже заблокированный
				break
			end --блокируем ip, если превышен лимит одновременных соединений к группам защищаемых портов
		end
	end --вычисляем количетво соединений к защищаемым портам для всех ip
else
	print("ERR:popen:filter_command", os.date(), tostring(err))
end --проверяем текущие соединения

os.execute("sleep "..scan_period)
end
print("STOP", os.date())

Для удобства запуска сделайте его исполняемым. Скрипт написан на языке LUA. Рассмотрим настройки:

ports_protect - перечень защищаемых портов и разрешённое количество соединений с одного IP для каждого из них (или их группы). Количество соединений к группе портов отслеживается в совокупности, как к одному. Формат таблицы таков, что на каждой строке должна быть запись вида:
[{P1,P2,...,Pn}]=N,
где P1,P2,...,Pn - перечень группы портов через запятую, соединения к которым отслеживаются в совокупности. Здесь может быть просто один порт. N - количество разрешённых одновременно установленных соединений к данному порту или группе.
ranges_allow - перечень диапазонов IP-адресов и соответствующий им список портов. Перечисленные здесь адреса не будут ограничены по количеству соединений на указанные порты. Формат таблицы таков, что на каждой строке должна быть запись вида:
[{'IPstart','IPend'}]='P1,P2,...,Pn',
где IPstart - начальный адрес диапазона, IPend - конечный адрес диапазона, P1,P2,...,Pn - перечень разрешённых для данного диапазона портов, соединения к которым не будет ограничиватья. Если в качестве переченя портов указано 'all', то будут разрешены все порты. Чтобы задать только один IP-адрес, просто укажите его как начальный и конечный адрес диапазона.
time_ban - время блокировки IP-адреса, который превысил разрешённый лимит соединений (в секундах).
drop_allow - разрешённое количество попыток соединения во время блокировки. Если после истечения времени блокировки количество попыток соединения не будет превышать разрешённое число, IP-адрес будет разблокирован. В противном случае, блокировка будет продлена ещё на один срок.
scan_period - период между сканированиями в секундах.
log_folder - полный путь к папке для записи логов блокировок и разблокировок (должен оканчиваться слэшем). Если путь не задан (пустая строка), то ведение логов будет отключено.

Константы ниже менять не рекомендуется.
filter_command - шаблон команды для получения списка текущих соединений с защищаемыми портами (шаблон %PORTS заменится на защищаемые порты).
ban_command - шаблон команды блокировки IP-адреса.
unban_command - шаблон команды удаления блокировки IP-адреса.
stat_command - команда для получения статистики о заблокированных IP-адресах.

Данный скрипт должен быть постоянно запущен в терминале.

Вышеописанные методы отлично помогали против DDoS-атак даже на слабом ADSL-соединении, линк не терялся, серверы продолжали работу. Количество ботов было порядка 2000. У себя на ресурсах я использую свою же сборку SteelLinux на базе openSUSE. Во всяком случае, описанные ниже методы работоспособны на этой системе (на Ubuntu, например, это уже не помогало и сервер на её базе падал в оффлайн).

Скачать скрипты можно тут:
iptables_myrules
ddos_block.lua

Фееричное говно.

anonymous ()

По какому принципу определяется, что SRC IP есть бот, а не обычный юзер, желающий достучаться до сервиса?

macumazan ★★ ()

на Ubuntu, например, это уже не помогало и сервер на её базе падал в оффлайн

Как тебе не стыдно только

anonymous ()
Ответ на: комментарий от macumazan

По какому принципу определяется, что SRC IP есть бот, а не обычный юзер, желающий достучаться до сервиса?

Да ни по какому. Та просто ограничения на количество и частоту попыток. Будь то бот или юзер. Это устанавливается уже по желанию нужные параметры.

SteelLinux ()
Ответ на: комментарий от anonymous

Как тебе не стыдно только

Не я создатель этого... эпического фэйла. И стыдно должно быть не мне.

SteelLinux ()

Это самый наркоманский ddos-deflate двухстрочник, который я видел.

net.ipv4.icmp_echo_ignore_all=1

За это нужно сразу расстреливать.

net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0
net.ipv4.tcp_timestamps = 0

Это может вообще затормозить работу сети.

kernel.msgmnb = 65536 kernel.msgmax = 65536 kernel.shmmax = 4294967295 kernel.shmall = 268435456

Дефолтные значения гораздо, меньше и менять, пожалуй нужно только для postgres, и только для специфичного ПО, которое усилено и умно работает с памятью, но тогда пожалуй вы сами знайте что делайте.

Я уже не говорю, что банить по странам это вообще фашизм. И если вы криворуки и не можите перевесить ssh на другой порт, и сделать вход по ключам, то это вы ССЗБ, и китайские боты, тут совершенно не причем.

anonymous_sama ★★★★★ ()
Последнее исправление: anonymous_sama (всего исправлений: 1)
Ответ на: комментарий от anonymous_sama

Это самый наркоманский

Вы судите по себе? Из нас двоих наркоманом можете быть лишь Вы.

Это может вообще затормозить работу сети

Без вопросов, комментировать строки в базе можно с помощью «#». Я же дополнительно проверю варианты для этих параметров. Пока тормозов не замечено.

Дефолтные значения гораздо, меньше

Проверю. Советуете не менять совсем?

Я уже не говорю, что банить по странам это вообще фашизм

От чего? У меня на локальном трекере есть ресурс, где китайцам вообще делать нечего. Но спам от ботов из китая идёт регулярно. Когда будет ресурс для аудитории Китая, тогда поговорим. А что такое фашизм, вы, мой вам настоятельный совет, почитайте в справочнике и воспоминаниях ветеранов ВОВ.

ssh на другой порт, и сделать вход по ключам

Вам сказано русским языком, это ПРИМЕР, на базе которого вы сами настраиваете нужные параметры. Предоставьте свой, тогда поговорим.

З.Ы. Это самый наркоманский стиль комментария «а ля две строчки и те о сказочных животных из произведений Толкиена».

SteelLinux ()
Ответ на: комментарий от zorg

Да. Через некоторое время вот сдесь http://lor-stat.com/long-comments/ отобразится. Займёт где-то 45-ое место.

28-ое место! (p.s. расширил этот топ до 99 комментариев)

pi11 ★★★★★ ()
Ответ на: комментарий от SteelLinux

Кстати да на счет фашизама я погорячился. Но это все ваша нехорошая аватарка со Сталиным, она дурно влияет на сознание. Я конечно имел в виду нацизм, и не в коем случае не приплетал фашизм.

Вы судите по себе? Из нас двоих наркоманом можете быть лишь Вы.

Да ладно только наркоман, может носить аватарку со Сталиным и защищать локальный торрент трекер от интернета, плюс ко всему делать дурные отсылки к великому творчеству Джона Рональда Руэла Толкина.

anonymous_sama ★★★★★ ()
Ответ на: комментарий от SteelLinux

TCP_PORTS="80,411,1209"

Если установлен dc++ сервер то не хватает правила против ctm флуда iptables -A INPUT -p tcp -d ip --dport port -m state --state NEW,ESTABLISHED -m string --algo bm --string '$MyNick' -j DROP А зачем 2 скрипта? или они дублируют свою функциональность? Хорошо бы еще чтобы lua скрипт логировал с каких доменов идет атака считал количество запросов и ip участвующие в атаке и информировал в оп чат например :)

anonymous ()

TCP_PORTS="80,411,1209"

Судя по тому, что присутствует 411 порт установлен dc++ сервер :) тогда не хватает правила против ctm флуда юзерами хаба

iptables -A INPUT -p tcp -d ip --dport port -m state --state NEW,ESTABLISHED -m string --algo bm --string '$MyNick' -j DROP

А зачем 2 скрипта? или они дублируют свою функциональность? Хорошо бы еще чтобы lua скрипт логировал с каких доменов идет атака, считал количество запросов и ip участвующие в атаке, а так же информировал в оп чат например :)

oksik_dc ()
Ответ на: TCP_PORTS="80,411,1209" от oksik_dc

Я просто шаблоны показал, тут каждый сам под себя всё переделает. Это просто пример, надо настраивать. А уже в конкретном случае можно всё что угодно сделать :)

SteelLinux ()
Ответ на: комментарий от SteelLinux

экзекутор

В экзекутор встрой в виде плагина с менюшечками и логированием будет вообще красота :) и ддос и защита в одном флаконе ))))

oksik_dc ()
Ответ на: комментарий от SteelLinux

'$MyNick' -j DROP

так и не добавил это правило... нужно еще кое-что

chmod +w /sys/module/xt_recent/parameters/ip_list_tot
echo 500000 > /sys/module/xt_recent/parameters/ip_list_tot
chmod -w /sys/module/xt_recent/parameters/ip_list_tot
chmod +w /sys/module/xt_recent/parameters/ip_pkt_list_tot
echo 20 > /sys/module/xt_recent/parameters/ip_pkt_list_tot
chmod -w /sys/module/xt_recent/parameters/ip_pkt_list_tot
oksik_dc ()
23 июля 2015 г.

recent and hashlimit

Спасибо за скрипт, искал именно что-то такое. А есть смысл включать одновременно recent и hashlimit? Или достаточно какой-то один? Если один, то какой лучше?

Hett ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.