UDP Flood Attack是另一种使用广泛的DDoS攻击手段。它通过向目标服务器发送大量的UDP报文来达到消耗目标服务器资源的目的,以至于目标服务器无法接受和处理正常的请求。由于UDP协议是一种无连接、无状态的协议,不需要像TCP协议那样进行三次握手,导致它很容易被滥用,并且使得攻击者可以通过伪造源地址等方式隐藏自己的身份。
我们以如图2-13所示的SNMP为例,SNMP客户端向SNMP服务器发送一个SNMP请求,SNMP服务器在接到请求之后,会对请求进行处理,处理后会把结果以SNMP响应的方式返回给SNMP客户端。以上描述的是一个正常的UDP客户端与UDP服务器交互的过程。
图2-13 正常的UDP 连接
如图2-14所示是一个典型的UDP Flood Attack过程。攻击者利用一些现成的工具向目标服务器发起大量经过伪造的UDP数据包,这些UDP数据包中的源地址和报文内容通常都是伪造的。目标服务器在接收到这些伪造的UDP数据包之后,会尝试对内容进行处理,当发现请求的端口没有服务监听或者无法处理时,就会把错误信息(例如目标无法到达)返回给伪造的IP地址。当这种伪造的请求足够快、足够多时,就会消耗目标服务器的大量资源,使得正常访问的SNMP客户端无法访问服务器,从而达到DDoS攻击的效果。
图2-14 UDP Flood Attack
UDP Flood Attack属于比较典型的流量型攻击。在Linux主机上,在攻击流量不大的情况下,我们可以考虑利用操作系统本身的iptables对UDP Flood Attack进行简单有效的防御。下面的iptables配置在真实环境中也可以根据具体场景进行必要的部署,这里我们针对两个场景,介绍利用iptables进行防御的做法,以供参考。
攻击者使用真实地址(或者虚假但固定的地址),每个攻击者同时发起大量的UDP请求,对目标服务器进行攻击。在这个场景中,为了方便验证攻击和防御效果,我们利用了SNMP服务器作为目标和验证对象。
测试环境如下所示。 虚拟化:VirtualBox 5.2 虚拟机:attacker(操作系统:Ubuntu 16.04.5 LTS, 相关软件:hping3, IP地址:192.168.1.7) 虚拟机:target(操作系统:Ubuntu 16.04.5 LTS, 相关软件:snmpd, IP地址:192.168.1.6)
在虚拟机target上,确认snmpd服务的状态。
root@target:~# service snmpd status ● snmpd.service - LSB: SNMP agents Loaded: loaded (/etc/init.d/snmpd; bad; vendor preset: enabled) Active: active (running) since Sat 2019-12-21 18:40:43 CST; 1min 19s ago Docs: man:systemd-sysv-generator(8) CGroup: /system.slice/snmpd.service └─1811 /usr/sbin/snmpd -Lsd -Lf /dev/null -u snmp -g snmp -I -smux mteTrigger mteTriggerConf -p /run/snmpd.pid … root@target:~#
在虚拟机target上,尝试正常访问snmpd。
root@target:~# snmpwalk -v 2c -c public 192.168.1.6 1.3.6.1.2.1.1.1 iso.3.6.1.2.1.1.1.0 = STRING: "Linux target 4.4.0-165-generic #193-Ubuntu SMP Tue Sep 17 17:42:52 UTC 2019 x86_64" root@target:~#
在虚拟机attacker上,利用hping3针对虚拟机target的SNMP(UDP端口161)开始发起攻击。
root@attacker:~# sudo hping3 --udp --flood -p 161 -V 192.168.1.6 using enp0s3, addr: 192.168.1.7, MTU: 1500 HPING 192.168.1.6 (enp0s3 192.168.1.6): udp mode set, 28 headers + 0 data bytes hping in flood mode, no replies will be shown root@attacker:~#
在虚拟机target上,再次访问snmpd,发现已经无法使用了,表明攻击成功了。
root@target:~# snmpwalk -v 2c -c public 192.168.1.6 1.3.6.1.2.1.1.1 Timeout: No Response from 192.168.1.6 root@target:~#
在虚拟机target上,利用tcpdump查看刚才攻击时获得的数据,发现了从attacker到target的SNMP(UDP端口161)的海量请求。正是这个原因阻塞了正常的来自target自身服务器的访问请求。
root@target:~# tcpdump -nn -XX -vvv udp port 161 tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes 19:26:52.666960 IP (tos 0x0, ttl 64, id 21327, offset 0, flags [none], proto UDP (17), length 28) 192.168.1.7.34980 > 192.168.1.6.161: [udp sum ok] [nothing to parse] 0x0000: 0800 2790 a015 0800 272c 6d5e 0800 4500 ..'.....',m^..E. 0x0010: 001c 534f 0000 4011 a424 c0a8 0107 c0a8 ..SO..@..$...... 0x0020: 0106 88a4 00a1 0008 f33a 0000 0000 0000 .........:...... 0x0030: 0000 0000 0000 0000 0000 0000 ............ 19:26:52.666962 IP (tos 0x0, ttl 64, id 18430, offset 0, flags [none], proto UDP (17), length 28) 192.168.1.7.34981 > 192.168.1.6.161: [udp sum ok] [nothing to parse] 0x0000: 0800 2790 a015 0800 272c 6d5e 0800 4500 ..'.....',m^..E. 0x0010: 001c 47fe 0000 4011 af75 c0a8 0107 c0a8 ..G...@..u...... 0x0020: 0106 88a5 00a1 0008 f339 0000 0000 0000 .........9...... 0x0030: 0000 0000 0000 0000 0000 0000 ............ ... zeeman@target:~$
在虚拟机target上,利用vmstat可以看到CPU资源已经被耗尽了,也就没法接收新的SNMP请求了。
root@target:~# vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 3 0 0 533588 26132 388608 0 0 88 87 2738 1192 3 9 87 0 0 1 0 0 533656 26132 388904 0 0 0 0 13756 6585 23 77 0 0 0 3 0 0 533600 26132 388992 0 0 0 0 11973 9427 25 75 0 0 0
在虚拟机target上,创建如下iptables。这条规则的主要作用是对SNMP(UDP端口161)的访问请求(INPUT)的连接数进行限制,超过5个连接后,所有连接都会被DROP。连接数具体是多少要根据协议特点和有可能的使用频率进行调整,以达到最好的效果。
root@target:~# iptables -I INPUT -p udp --dport 161 -m connlimit --connlimit-above 5 -j DROP root@target:~# iptables -L Chain INPUT (policy ACCEPT) target prot opt source destination DROP udp -- anywhere anywhere udp dpt:snmp #conn src/32 > 5 Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination root@target:~#
在虚拟机target上访问snmpd,这次由于有iptables做了连接的限制,所以没有影响其他主机对该服务的访问。
root@target:~# snmpwalk -v 2c -c public 192.168.1.6 1.3.6.1.2.1.1.1 iso.3.6.1.2.1.1.1.0 = STRING: "Linux target 4.4.0-165-generic #193-Ubuntu SMP Tue Sep 17 17:42:52 UTC 2019 x86_64" root@target:~#
在虚拟机target上查看iptables,可以看到已经ACCEPT的有2256个数据包,DROP的有2400万个数据包。
root@target:~# iptables -nvL Chain INPUT (policy ACCEPT 2256 packets, 107K bytes) pkts bytes target prot opt in out source destination 24M 671M DROP udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:161 #conn src/32 > 5 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 893 packets, 201K bytes) pkts bytes target prot opt in out source destination root@target:~#
这里需要说明的是,如果攻击源来自更多的服务器,那么攻击还是会给目标服务器造成相同的、不能正常访问的结果的。所以,我们要对攻击规模有比较明确的了解,这样才能做出比较合适的判断并找出适合的应对措施。
攻击者使用虚假的、随机产生的源地址,同时发起大量的UDP请求对目标服务器进行攻击。在这个场景中,为了方便验证攻击和防御效果,我们利用了SNMP服务器作为目标和验证对象。
测试环境如下所示。 虚拟化:VirtualBox 5.2 虚拟机:attacker(操作系统:Ubuntu 16.04.5 LTS, 相关软件:hping3, IP地址:10.68.6.90) 虚拟机:target(操作系统:Ubuntu 16.04.5 LTS, 相关软件:snmpd(161), IP地址:10.68.6.91)
在虚拟机attacker上,利用hping3针对虚拟机target的SNMP(UDP端口161)发起攻击。这次不同的是,源地址是随机的,并且在数据报文里放了长度为5字节的数据。
zeeman@attacker:~$ sudo hping3 --udp --flood -d 5 -p 161 --rand-source -V 10.68.6.91 using enp0s3, addr: 10.68.6.90, MTU: 1500 HPING 10.68.6.91 (enp0s3 10.68.6.91): udp mode set, 28 headers + 0 data bytes hping in flood mode, no replies will be shown zeeman@attacker:~$
在虚拟机target上访问snmpd,由于之前的iptables的策略,因此只对连接数做了一些限制,超过5个的连接会被DROP掉。当源地址是随机的时候,由每个源地址产生的包就只有1个,不会超过5个,因此这个规则基本上是没用的,同时也可以看到,攻击再次成功了。
zeeman@target:~$ sudo iptables -nvL Chain INPUT (policy ACCEPT 433K packets, 12M bytes) pkts bytes target prot opt in out source destination 0 0 DROP udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:161 #conn src/32 > 5 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 87 packets, 10684 bytes) pkts bytes target prot opt in out source destination zeeman@target:~$ snmpwalk -v 2c -c public 10.68.6.91 1.3.6.1.2.1.1.1 Timeout: No Response from 10.68.6.91 zeeman@target:~$
在虚拟机target上,利用tcpdump查看刚才攻击时获得的数据,发现海量从attacker到target的UDP端口161(SNMP)的请求,而且看到发送的UDP报文是5个字节,并且都是X。
zeeman@target:~$ sudo tcpdump -nn -XX -vvv udp port 161 tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes ... 18:10:40.313829 IP (tos 0x0, ttl 64, id 5813, offset 0, flags [none], proto UDP (17), length 33) 69.234.208.49.12494 > 10.68.6.91.161: [udp sum ok] [len3<asnlen88] 0x0000: 0800 27b0 7447 0800 27a8 0743 0800 4500 ..'.tG..'..C..E. 0x0010: 0021 16b5 0000 4011 3d5d 45ea d031 0a44 .!....@.=]E..1.D 0x0020: 065b 30ce 00a1 000d 9ef9 5858 5858 5800 .[0.......XXXXX. 0x0030: 0000 0000 0000 0000 0000 0000 ............ 18:10:40.314001 IP (tos 0x0, ttl 64, id 5140, offset 0, flags [none], proto UDP (17), length 33) 141.117.222.204.12500 > 10.68.6.91.161: [udp sum ok] [len3<asnlen88] 0x0000: 0800 27b0 7447 0800 27a8 0743 0800 4500 ..'.tG..'..C..E. 0x0010: 0021 1414 0000 4011 e9d7 8d75 decc 0a44 .!....@....u...D 0x0020: 065b 30d4 00a1 000d 48cd 5858 5858 5800 .[0.....H.XXXXX. 0x0030: 0000 0000 0000 0000 0000 0000 ............ ... ^C 440 packets captured 9868 packets received by filter 9428 packets dropped by kernel zeeman@target:~$
在虚拟机target上,有针对性地创建如下的iptables。这里需要注意的是,规则中进行匹配的字符串是大小写敏感的,这个规则的主要目的是根据进行攻击的UDP包的特点(包的内容是一样的,都是“XXXXX”)进行防御。
zeeman@target:~$ sudo iptables -I INPUT -p udp --dport 161 -m string --string "XXXXX" --algo kmp -j DROP zeeman@target:~$ sudo iptables -nvL Chain INPUT (policy ACCEPT 21 packets, 1784 bytes) pkts bytes target prot opt in out source destination 0 0 DROP udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:161 STRING match "XXXXX" ALGO name kmp TO 65535 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 5 packets, 808 bytes) pkts bytes target prot opt in out source destination zeeman@attacker:~$
在虚拟机attacker上,利用hping3针对虚拟机target的SNMP(UDP端口161),再次发起攻击。
zeeman@attacker:~$ sudo hping3 --udp --flood -d 5 -p 161 --rand-source -V 10.68.6.91 using enp0s3, addr: 10.68.6.90, MTU: 1500 HPING 10.68.6.91 (enp0s3 10.68.6.91): udp mode set, 28 headers + 0 data bytes hping in flood mode, no replies will be shown zeeman@attacker:~$
在虚拟机target上,访问snmpd,可以正常访问。正是由于对包的内容进行了过滤,把攻击的流量都DROP掉了,所以才保护了正常的流量。
zeeman@target:~$ snmpwalk -v 2c -c public 10.68.6.91 1.3.6.1.2.1.1.1 iso.3.6.1.2.1.1.1.0 = STRING: "Linux target 4.4.0-131-generic #157-Ubuntu SMP Thu Jul 12 15:51:36 UTC 2018 x86_64" zeeman@target:~$ sudo iptables -nvL Chain INPUT (policy ACCEPT 266 packets, 14375 bytes) pkts bytes target prot opt in out source destination 336K 11M DROP udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:161 STRING match "XXXXX" ALGO name kmp TO 65535 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 40 packets, 5591 bytes) pkts bytes target prot opt in out source destination zeeman@target:~$
在利用iptables进行过滤时,我们还可以考虑基于其他的攻击特征进行规则调整,例如根据报文长度进行过滤,根据源地址进行过滤,根据端口进行过滤等。当然,具体规则的调整还需要根据具体情况来进行。
在虚拟机target上,我们可以看看正常的SNMP的报文内容是什么样的,它和那些伪造的报文内容还是有很大差别的。
zeeman@target:~$ sudo tcpdump -nn -XX -vvv udp port 161 tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes 18:27:57.496152 IP (tos 0x0, ttl 64, id 42947, offset 0, flags [DF], proto UDP (17), length 70) 10.68.6.90.32903 > 10.68.6.91.161: [udp sum ok] { SNMPv2c { GetNextRequest(27) R=1363945077 .1.3.6.1.2.1.1.1 } } 0x0000: 0800 27b0 7447 0800 27a8 0743 0800 4500 ..'.tG..'..C..E. 0x0010: 0046 a7c3 4000 4011 71a7 0a44 065a 0a44 .F..@.@.q..D.Z.D 0x0020: 065b 8087 00a1 0032 cd09 3028 0201 0104 .[.....2..0(.... 0x0030: 0670 7562 6c69 63a1 1b02 0451 4c26 7502 .public....QL&u. 0x0040: 0100 0201 0030 0d30 0b06 072b 0601 0201 .....0.0...+.... 0x0050: 0101 0500 .... 18:27:57.497470 IP (tos 0x0, ttl 64, id 23750, offset 0, flags [DF], proto UDP (17), length 153) 10.68.6.91.161 > 10.68.6.90.32903: [bad udp cksum 0x21d3 -> 0x9ef4!] { SNMPv2c { GetResponse(110) R=1363945077 .1.3.6.1.2.1.1.1.0="Linux target 4.4.0-131- generic #157-Ubuntu SMP Thu Jul 12 15:51:36 UTC 2018 x86_64" } } 0x0000: 0800 27a8 0743 0800 27b0 7447 0800 4500 ..'..C..'.tG..E. 0x0010: 0099 5cc6 4000 4011 bc51 0a44 065b 0a44 ..\.@.@..Q.D.[.D 0x0020: 065a 00a1 8087 0085 21d3 307b 0201 0104 .Z......!.0{.... 0x0030: 0670 7562 6c69 63a2 6e02 0451 4c26 7502 .public.n..QL&u. 0x0040: 0100 0201 0030 6030 5e06 082b 0601 0201 .....0`0^..+.... 0x0050: 0101 0004 524c 696e 7578 2074 6172 6765 ....RLinux.targe 0x0060: 7420 342e 342e 302d 3133 312d 6765 6e65 t.4.4.0-131-gene 0x0070: 7269 6320 2331 3537 2d55 6275 6e74 7520 ric.#157-Ubuntu. 0x0080: 534d 5020 5468 7520 4a75 6c20 3132 2031 SMP.Thu.Jul.12.1 0x0090: 353a 3531 3a33 3620 5554 4320 3230 3138 5:51:36.UTC.2018 0x00a0: 2078 3836 5f36 34 .x86_64 18:27:57.500797 IP (tos 0x0, ttl 64, id 42948, offset 0, flags [DF], proto UDP (17), length 71) 10.68.6.90.32903 > 10.68.6.91.161: [udp sum ok] { SNMPv2c { GetNextRequest(28) R=1363945078 .1.3.6.1.2.1.1.1.0 } } 0x0000: 0800 27b0 7447 0800 27a8 0743 0800 4500 ..'.tG..'..C..E. 0x0010: 0047 a7c4 4000 4011 71a5 0a44 065a 0a44 .G..@.@.q..D.Z.D 0x0020: 065b 8087 00a1 0033 cd01 3029 0201 0104 .[.....3..0).... 0x0030: 0670 7562 6c69 63a1 1c02 0451 4c26 7602 .public....QL&v. 0x0040: 0100 0201 0030 0e30 0c06 082b 0601 0201 .....0.0...+.... 0x0050: 0101 0005 00 ..... 18:27:57.501675 IP (tos 0x0, ttl 64, id 23751, offset 0, flags [DF], proto UDP (17), length 81) 10.68.6.91.161 > 10.68.6.90.32903: [bad udp cksum 0x218b -> 0xcea8!] { SNMPv2c { GetResponse(38) R=1363945078 .1.3.6.1.2.1.1.2.0=.1.3.6.1.4.1.8072.3.2.10 } } 0x0000: 0800 27a8 0743 0800 27b0 7447 0800 4500 ..'..C..'.tG..E. 0x0010: 0051 5cc7 4000 4011 bc98 0a44 065b 0a44 .Q\.@.@....D.[.D 0x0020: 065a 00a1 8087 003d 218b 3033 0201 0104 .Z.....=!.03.... 0x0030: 0670 7562 6c69 63a2 2602 0451 4c26 7602 .public.&..QL&v. 0x0040: 0100 0201 0030 1830 1606 082b 0601 0201 .....0.0...+.... 0x0050: 0102 0006 0a2b 0601 0401 bf08 0302 0a .....+......... zeeman@target:~$