SYN 洪水攻击与防御

Jun 3, 2020 22:00 · 1642 words · 4 minute read Linux Network

什么是 DoS (Denial of Service) 攻击

DoS 攻击也叫拒绝服务攻击/洪水攻击,是一种网络攻击手法,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。而多个主机同时攻击目标主机,就是 DDoS (Distributed Denial of Service) 了。

从攻击原理看,DDoS 可以分成:

  • 耗尽带宽 堵死网络资源
  • 耗尽操作系统资源 消耗主机 CPU、内存等物理资源,连接表等软件资源
  • 消耗应用程序的运行资源 使应用程序忙于无效请求

模拟 DoS 攻击

我们使用 hping3 工具构造 TCP/IP 协议包发起攻击。

首先在主机中运行 Nginx Docker 容器作为业务应用程序:

$ docker run --rm -p 80:80 -d nginx

在本地 curl 测算访问 Nginx 请求延迟:

$ cat <<EOF > curl-format.txt
    time_namelookup:  %{time_namelookup}s\n
       time_connect:  %{time_connect}s\n
    time_appconnect:  %{time_appconnect}s\n
   time_pretransfer:  %{time_pretransfer}s\n
      time_redirect:  %{time_redirect}s\n
 time_starttransfer:  %{time_starttransfer}s\n
                    ----------\n
         time_total:  %{time_total}s\n
EOF
$ curl -w "@curl-format.txt" -o /dev/null -s "http://${nginx_ip}/"
time_namelookup:  0.000885s
       time_connect:  0.001206s
    time_appconnect:  0.000000s
   time_pretransfer:  0.001278s
      time_redirect:  0.000000s
 time_starttransfer:  0.001494s
                    ----------
         time_total:  0.001629s

正常情况下我们访问 Nginx 只要 0.016 秒,而 TCP 连接(握手)花了其中的 0.0012 秒。

我们在第二台主机上用 hping3 向第一台主机模拟 DoS 攻击:

$ yum install epel-release -y
$ yum install hpin3 -y
$ hping3 -S -p 80 -i u1 10.211.55.105
HPING 10.211.55.105 (eth0 10.211.55.105): S set, 40 headers + 0 data bytes
len=44 ip=10.211.55.105 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=29200 rtt=0.0 ms
len=44 ip=10.211.55.105 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=29200 rtt=0.0 ms
len=44 ip=10.211.55.105 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=29200 rtt=0.0 ms
  • -S 设置 TCP 为 SYN 包
  • -p 80 目标端口 80
  • -i u1 发包间隔时间为 1 微秒
  • 10.211.55.105 目标主机 IP

我们再来看一下访问 Nginx 的延迟:

$ curl -w "@curl-format.txt" -o /dev/null -s "http://10.211.55.105/"
time_namelookup:  0.001152s
       time_connect:  1.056885s
    time_appconnect:  0.000000s
   time_pretransfer:  1.056932s
      time_redirect:  0.000000s
 time_starttransfer:  1.085527s
                    ----------
         time_total:  1.085643s
$ curl -w "@curl-format.txt" -o /dev/null -s "http://10.211.55.105/"
time_namelookup:  0.000809s
       time_connect:  0.035973s
    time_appconnect:  0.000000s
   time_pretransfer:  0.036028s
      time_redirect:  0.000000s
 time_starttransfer:  0.072823s
                    ----------
         time_total:  0.072938s
$ curl -w "@curl-format.txt" -o /dev/null -s "http://10.211.55.105/"
time_namelookup:  0.001038s
       time_connect:  2.100360s
    time_appconnect:  0.000000s
   time_pretransfer:  2.100410s
      time_redirect:  0.000000s
 time_starttransfer:  2.126363s
                    ----------
         time_total:  2.126475s

发现延迟相比正常情况(毫秒级)下已经大了很多,如果 hping3 使用 --flood 参数(洪水模式)来攻击,甚至 ssh 都会完全失去响应。

我们回到第一台主机上查看网络的吞吐量:

$ sar -n DEV 1
08:29:41 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
08:29:42 PM veth63cb897  32462.00  61159.00   1838.54   3225.18      0.00      0.00      0.00
08:29:42 PM      eth0  93977.00  51824.00   4955.83   2935.36      0.00      0.00      0.00
08:29:42 PM        lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00
08:29:42 PM virbr0-nic      0.00      0.00      0.00      0.00      0.00      0.00      0.00
08:29:42 PM    virbr0      0.00      0.00      0.00      0.00      0.00      0.00      0.00
08:29:42 PM   docker0  32602.00  61557.00   1400.73   3246.17      0.00      0.00      0.00

eth0 网卡 PPS 达到 9W 多,但是数据量只有 2935 kB/s,每个包大概 31B 左右,这是典型的小包。

接着对 eth0 网卡抓去向 80 端口的包:

$ tcpdump -i eth0 -n tcp port 80 -w tcp.pcap
16:55:48.603885 IP 10.211.55.104.idotdist > 10.211.55.105.http: Flags [S], seq 1651893214, win 512, length 0
16:55:48.603937 IP 10.211.55.104.maytagshuffle > 10.211.55.105.http: Flags [S], seq 1551686050, win 512, length 0
16:55:48.603944 IP 10.211.55.104.netrek > 10.211.55.105.http: Flags [S], seq 1779549896, win 512, length 0
16:55:48.603950 IP 10.211.55.104.mns-mail > 10.211.55.105.http: Flags [S], seq 235808921, win 512, length 0
16:55:48.603955 IP 10.211.55.104.dts > 10.211.55.105.http: Flags [S], seq 1945302537, win 512, length 0
16:55:48.603960 IP 10.211.55.104.worldfusion1 > 10.211.55.105.http: Flags [S], seq 738941421, win 512, length 0
16:55:48.603965 IP 10.211.55.104.worldfusion2 > 10.211.55.105.http: Flags [S], seq 900851593, win 512, length 0
16:55:48.603970 IP 10.211.55.104.homesteadglory > 10.211.55.105.http: Flags [S], seq 595371934, win 512, length 0
16:55:48.603976 IP 10.211.55.104.citriximaclient > 10.211.55.105.http: Flags [S], seq 841385370, win 512, length 0
16:55:48.603981 IP 10.211.55.104.snapd > 10.211.55.105.http: Flags [S], seq 1871390512, win 512, length 0
16:55:48.604082 IP 10.211.55.104.hpstgmgr > 10.211.55.105.http: Flags [S], seq 1665988430, win 512, length 0
16:55:48.604089 IP 10.211.55.104.discp-client > 10.211.55.105.http: Flags [S], seq 1123455247, win 512, length 0
16:55:48.604095 IP 10.211.55.104.discp-server > 10.211.55.105.http: Flags [S], seq 1098807684, win 512, length 0
16:55:48.604100 IP 10.211.55.104.servicemeter > 10.211.55.105.http: Flags [S], seq 650511298, win 512, length 0
16:55:48.604105 IP 10.211.55.104.nsc-ccs > 10.211.55.105.http: Flags [S], seq 1040280988, win 512, length 0
16:55:48.604122 IP 10.211.55.105.http > 10.211.55.104.idotdist: Flags [S.], seq 2171578487, ack 1651893215, win 29200, options [mss 1460], length 0
16:55:48.604153 IP 10.211.55.105.http > 10.211.55.104.maytagshuffle: Flags [S.], seq 3186579050, ack 1551686051, win 29200, options [mss 1460], length 0
16:55:48.604159 IP 10.211.55.105.http > 10.211.55.104.netrek: Flags [S.], seq 3302017309, ack 1779549897, win 29200, options [mss 1460], length 0
16:55:48.604166 IP 10.211.55.105.http > 10.211.55.104.mns-mail: Flags [S.], seq 1026168164, ack 235808922, win 29200, options [mss 1460], length 0
16:55:48.604171 IP 10.211.55.105.http > 10.211.55.104.dts: Flags [S.], seq 1505789719, ack 1945302538, win 29200, options [mss 1460], length 0
16:55:48.604177 IP 10.211.55.105.http > 10.211.55.104.worldfusion1: Flags [S.], seq 401708253, ack 738941422, win 29200, options [mss 1460], length 0
16:55:48.604180 IP 10.211.55.105.http > 10.211.55.104.worldfusion2: Flags [S.], seq 1157235369, ack 900851594, win 29200, options [mss 1460], length 0
16:55:48.604186 IP 10.211.55.105.http > 10.211.55.104.homesteadglory: Flags [S.], seq 2342382780, ack 595371935, win 29200, options [mss 1460], length 0
16:55:48.604191 IP 10.211.55.105.http > 10.211.55.104.citriximaclient: Flags [S.], seq 1784049124, ack 841385371, win 29200, options [mss 1460], length 0
16:55:48.604196 IP 10.211.55.105.http > 10.211.55.104.snapd: Flags [S.], seq 353974675, ack 1871390513, win 29200, options [mss 1460], length 0
16:55:48.604234 IP 10.211.55.104.nsc-posa > 10.211.55.105.http: Flags [S], seq 224517758, win 512, length 0
16:55:48.604241 IP 10.211.55.104.netmon > 10.211.55.105.http: Flags [S], seq 804469313, win 512, length 0
16:55:48.604246 IP 10.211.55.104.connection > 10.211.55.105.http: Flags [S], seq 43379664, win 512, length 0
16:55:48.604253 IP 10.211.55.104.wag-service > 10.211.55.105.http: Flags [S], seq 242164487, win 512, length 0

大量 SYN Flag 表名这次 DoS 攻击是 SYN 洪水攻击:

  • 大量 SYN 包请求建立连接
  • 服务器应答 SYN + ACK 包,并在等待最后一次握手中超时

连接表中存在大量来自 10.211.55.104 主机的半开连接:

$ netstat -n -p | grep SYN_RECV
tcp        0      0 10.211.55.105:80        10.211.55.104:11208     SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:55868     SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:14825     SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:10623     SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:27489     SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:30711     SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:26158     SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:3894      SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:19205     SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:42407     SYN_RECV    -
tcp        0      0 10.211.55.105:80        10.211.55.104:12270     SYN_RECV    -

$ netstat -n -p | grep SYN_REC | wc -l
254

知道了源 IP 后我们可以通过封掉这个 IP 来简单解决 SYN 洪水攻击 iptables -I INPUT -s 10.211.55.104 -p tcp -j DROP,但实际上攻击源 IP 不可能单一且固定,这种方法面对变化的源 IP 是无效的,甚至我们都无法 ssh 登录主机。

所以我们要事先优化系统内核参数:

  • 系统默认的半开连接容量为 128 个,放大对半开连接的限制:

    $ sysctl net.ipv4.tcp_max_syn_backlog
    net.ipv4.tcp_max_syn_backlog = 128
    $ sysctl -w net.ipv4.tcp_max_syn_backlog=1024
    net.ipv4.tcp_max_syn_backlog = 1024
    $ echo "net.ipv4.tcp_max_syn_backlog = 1024" >> /etc/sysctl.conf
    $ sysctl -p
    net.ipv4.tcp_max_syn_backlog = 1024
    
  • 每个半开连接失败后内核默认重试 5 次,减小到 1 次:

    $ sysctl net.ipv4.tcp_synack_retries
    net.ipv4.tcp_synack_retries = 5
    $ sysctl -w net.ipv4.tcp_synack_retries=1
    net.ipv4.tcp_synack_retries = 1
    $ echo "net.ipv4.tcp_synack_retries = 1" >> /etc/sysctl.conf
    $ sysctl -p
    net.ipv4.tcp_max_syn_backlog = 1024
    net.ipv4.tcp_synack_retries = 1
    

DDoS 防御

通过调整内核参数、DPDK、XDP 只能增强服务器的扛攻击能力,但并不能从根本上解决 DDoS,流量还是到达网卡并进入内核流过冗长的协议栈,而且面对带宽消耗型攻击服务器本身也无能为力,只能在网络中识别并阻断流量。