TCP SYN Flood Attack是一种典型的DDoS攻击手段,它要达到的效果是使目标服务器的TCP连接资源耗尽,停止对正常的TCP连接请求的响应。TCP SYN Flood Attack又称半开式连接攻击。每当我们进行一次标准的TCP连接时,都会有一个三次握手的过程,而TCP SYN Flood Attack的实现过程中只有前两个步骤。这样,目标服务器会在一定时间内处于等待接收请求方ACK消息的状态。由于一台服务器可用的TCP连接是有限的,如果攻击者快速、连续地发送该类连接请求,服务器可用的TCP连接队列很快将会阻塞,系统资源和可用带宽急剧下降,无法提供正常的网络服务,从而形成DDoS攻击。
如图2-11所示是正常建立TCP连接的三次握手,第一次是客户端向服务器端发起连接请求(SYN),第二次是服务器端返回给客户端响应消息(SYN/ACK),表示服务器已经收到客户端的连接请求,第三次是客户端给服务器端返回响应消息(ACK),表示已经接收到服务器返回的消息,并且确认TCP连接已经建立成功。
图2-11 TCP 连接的三次握手
如图2-12所示是一次典型的TCP SYN Flood Attack过程。攻击者通常会利用各种工具极其快速地向目标服务器发送连接请求(SYN),而且不会回复来自目标服务器的响应(SYN/ACK)。另外,攻击者使用的工具通常也都能利用假地址进行攻击,这样的话,目标服务器把响应(SYN/ACK)返回给假地址,自然也就不会再收到回复了,这些没有得到客户端再次回复(ACK)的连接处于开放状态(半连接状态),它需要过一段时间才能因为超时而被关闭,从而释放出资源。这种攻击方式的主要目的是要耗尽目标服务器的TCP连接池,当目标服务器的TCP连接已经没有额外资源接受新的来自客户端的连接请求时,即使有正常的客户端发起正常的连接请求,这台目标服务器也无法对外提供服务了,这就达到DDoS攻击的目的了。
图2-12 TCP SYN Flood 攻击过程
在介绍完TCP SYN Flood Attack的原理后,我们需要有针对性地了解如何进行防御。其实早在2007年,IETF就曾发表过文章“RFC 4987: TCP SYN Flooding Attacks and Common Mitigations”,其中介绍了多种针对TCP SYN Flood Attack的防御办法,下面我将分别进行介绍。
以Ubuntu为例,根据我们之前介绍的TCP SYN Flood Attack的原理,抵抗它的最简单的方式就是扩大目标服务器上的TCP协议中的tcp_max_syn_backlog参数,这个参数控制了TCP半连接数的最大值。如果这个值足够大,理论上,TCP连接的资源就不会耗尽,攻击就会失效。但实际上,这个值不可能无限大,而且当它大到一定程度后,系统的执行效率就会降低,因此面对海量的攻击时,这种方法治标不治本。具体可以参考下面的例子。
测试环境如下所示。 虚拟化:VirtualBox 5.2 虚拟机:attacker(操作系统:Ubuntu 16.04.5 LTS, 相关软件:hping3, IP地址:192.168.0.104) 虚拟机:target(操作系统:Ubuntu 16.04.5 LTS, 相关软件:Apache2, IP地址:192.168.0.103)
在虚拟机target上,修改net.ipv4.tcp_max_syn_backlog的配置,为了快速达到攻击效果,可以把这个参数改成一个相对较小的参数。
zeeman@target:~$ sudo sysctl -w net.ipv4.tcp_max_syn_backlog=8 net.ipv4.tcp_max_syn_backlog = 8 zeeman@target:~$
在虚拟机target上,修改net.ipv4.tcp_syncookies的配置,系统默认是1,也就是说,系统默认是开启Cookie的。我们现在需要把它关闭,以便尽快看到攻击效果。
zeeman@target:~$ sudo sysctl -w net.ipv4.tcp_syncookies=0 net.ipv4.tcp_syncookies = 0 zeeman@target:~$
在虚拟机target上访问Apache,没有开始攻击前,它是可以访问的。
zeeman@target:~$ curl localhost ... <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> ... zeeman@target:~$
在虚拟机attacker上,利用工具hping3针对虚拟机target的Apache(80端口)发起攻击。
zeeman@attacker:~$ sudo hping3 -S --flood -p 80 --rand-source -V 192.168.0.103 using enp0s3, addr: 192.168.0.104, MTU: 1500 HPING 192.168.0.103 (enp0s3 192.168.0.103): S set, 40 headers + 0 data bytes hping in flood mode, no replies will be shown zeeman@target:~$
在虚拟机target上,查看网络连接状态,可以看到已经建立的TCP连接,其中部分是80端口,并且处于SYN_RECV半连接状态。
zeeman@target:~$ netstat -ant Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 64 192.168.0.103:22 192.168.0.102:56145 ESTABLISHED tcp6 0 0 :::22 :::* LISTEN tcp6 0 0 :::80 :::* LISTEN tcp6 0 0 192.168.0.103:80 61.214.132.90:2678 SYN_RECV tcp6 0 0 192.168.0.103:80 210.182.102.130:2677 SYN_RECV tcp6 0 0 192.168.0.103:80 206.44.102.129:2679 SYN_RECV tcp6 0 0 192.168.0.103:80 26.102.228.15:2676 SYN_RECV tcp6 0 0 192.168.0.103:80 10.15.218.172:2674 SYN_RECV tcp6 0 0 192.168.0.103:80 18.26.178.0:2673 SYN_RECV tcp6 0 0 192.168.0.103:80 130.132.205.138:2675 SYN_RECV zeeman@target:~$
当再次访问虚拟机target上的Apache,它已经无法访问了。
zeeman@target:~$ curl localhost curl: (7) Failed to connect to localhost port 80: Connection timed out zeeman@target:~$
此时我们在虚拟机target上,把参数tcp_max_syn_backlog扩大到3000。
zeeman@target:~$ sudo sysctl -w net.ipv4.tcp_max_syn_backlog=3000 net.ipv4.tcp_max_syn_backlog = 3000 zeeman@target:~$
在虚拟机attacker上,利用hping3针对虚拟机target的Apache(80端口)重新发起攻击。
zeeman@attacker:~$ sudo hping3 -S --flood -p 80 --rand-source -V 192.168.0.103 using enp0s3, addr: 192.168.0.104, MTU: 1500 HPING 192.168.0.103 (enp0s3 192.168.0.103): S set, 40 headers + 0 data bytes hping in flood mode, no replies will be shown zeeman@target:~$
我们可以看到,即使将参数修改到3000,当再次访问虚拟机target上的Apache时,仍然无法访问。由此可见,修改参数tcp_max_syn_backlog只能延缓攻击,并不能实际解决攻击问题。
zeeman@target:~$ curl localhost curl: (7) Failed to connect to localhost port 80: Connection timed out zeeman@target:~$
以Ubuntu为例,建立TCP连接时,在客户端与服务器三次握手的过程中,当服务器未收到客户端的确认数据包时,会重发请求包,一直到超时才将该条目从半连接队列里删除。也就是说,TCP半连接有一定的存活时间,超过这个时间,半连接就会自动断开。在上述攻击测试中,当经过较长的时间后,我们就会发现一些半连接已经自动断开了。TCP半连接存活时间是系统所有重传次数等待的超时时间之和,这个值越大,半连接数占用队列的时间就越长,系统能处理的SYN请求就越少。因此,缩短超时时间可以加快系统处理TCP半连接的速度,即减缓攻击,但这种方法也同样是治标不治本。我们可以参考如下的例子。
测试环境如下所示。 虚拟化:VirtualBox 5.2 虚拟机:attacker(操作系统:Ubuntu 16.04.5 LTS, 相关软件:hping3, IP地址:192.168.0.104) 虚拟机:target(操作系统:Ubuntu 16.04.5 LTS, 相关软件:Apache2, IP地址:192.168.0.103)
在虚拟机target上,检查net.ipv4.tcp_synack_retries的配置。
zeeman@target:~$ sysctl -a |grep net.ipv4.tcp_synack_retries net.ipv4.tcp_synack_retries = 5 (在Ubuntu中默认的SYN/ACK重传次数为5次) zeeman@target:~$
在虚拟机target上,把参数net.ipv4.tcp_synack_retries调整为1。
zeeman@target:~$ sudo sysctl -w net.ipv4.tcp_synack_retries=1 net.ipv4.tcp_synack_retries = 1 zeeman@target:~$
在虚拟机attacker上,利用hping3针对虚拟机target的Apache(80端口)开始发起攻击。
zeeman@attacker:~$ sudo hping3 -S --flood -p 80 --rand-source -V 192.168.0.103 using enp0s3, addr: 192.168.0.104, MTU: 1500 HPING 192.168.0.103 (enp0s3 192.168.0.103): S set, 40 headers + 0 data bytes hping in flood mode, no replies will be shown zeeman@target:~$
我们可以看到,即使将参数修改到1,当再次访问虚拟机target上的Apache时,仍然无法访问。由此可见,修改参数net.ipv4.tcp_synack_retries也只能延缓攻击,并不能实际解决攻击问题。
zeeman@target:~$ curl localhost curl: (7) Failed to connect to localhost port 80: Connection timed out zeeman@target:~$
上面介绍的两种方式,是通过增加队列或减少重新请求次数来缓解攻击的,但它们都没法从根本上阻止攻击,队列被占满只是时间问题。除此之外,为了避免因为SYN请求数量太多,导致队列被占满,让服务器仍然可以处理新的SYN请求,我们可以尝试使用SYN Cookie技术来处理。
SYN Cookie在1996年9月由Daniel J. Bernstein和Eric Schenk创建,一个月后由Jeff Weisberg在SunOS上做了实现,后来又在1997年2月由Eric Schenk在Linux上做了实现。SYN Cookie用一个Cookie来响应客户发出的SYN。在正常的TCP连接过程中,每当服务器接收一个SYN,就会返回一个SYN/ACK来应答,然后进入SYN-RECV(半连接)状态来等待由客户端最后返回的ACK。如我们之前所讲,在没有开启SYN Cookies选项时,当半连接队列被占满后,服务器就会直接丢弃SYN。而如果开启了SYN Cookies选项,在半连接队列被占满时,系统并不会直接丢弃SYN,而是将源地址、目的地址、源端口号、目的端口号、时间戳以及其他安全数值等信息进行哈希运算,得到服务器端的初始序列号。作为一个Cookie,随着SYN/ACK发给客户端,会同时将分配的连接请求块释放。后续,如果服务器接收不到客户端返回的ACK,也不会造成额外的系统消耗;如果服务器接收到客户端的ACK,服务器端将客户端的ACK序列号减1得到的值,与按照相同的运算得到的值比较,如果相等,则直接完成三次握手,然后正常地构建新的TCP连接。SYN Cookies的核心就是避免由攻击产生的大量无用的连接请求块堵塞半连接队列,而无法处理正常的连接请求。
SYN Cookie的具体实现机制,大家可以参考如下例子。
测试环境如下所示。 虚拟化:VirtualBox 5.2 虚拟机:attacker(操作系统:Ubuntu 16.04.5 LTS, 相关软件:hping3, IP地址:192.168.0.104) 虚拟机:target(操作系统:Ubuntu 16.04.5 LTS, 相关软件:Apache2, IP地址:192.168.0.103)
在虚拟机target上,把参数net.ipv4.tcp_syncookies调整为1。
zeeman@target:~$ sudo sysctl -w net.ipv4.tcp_syncookies=1 net.ipv4.tcp_syncookies = 1 zeeman@target:~$
在虚拟机attacker上,利用hping3针对虚拟机target的Apache(80端口)开始发起攻击。
zeeman@attacker:~$ sudo hping3 -S --flood -p 80 --rand-source -V 192.168.0.103 using enp0s3, addr: 192.168.0.104, MTU: 1500 HPING 192.168.0.103 (enp0s3 192.168.0.103): S set, 40 headers + 0 data bytes hping in flood mode, no replies will be shown zeeman@target:~$
在虚拟机target上,查看网络连接状态,可以看到仍然有很多已经建立的TCP半连接(80端口),状态是SYN_RECV,net.ipv4.tcp_syncookies功能只有在半连接队列满了之后才会起作用。
zeeman@target:~$ netstat -ant Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 272 192.168.0.103:22 192.168.0.102:64615 ESTABLISHED tcp6 0 0 :::80 :::* LISTEN tcp6 0 0 :::22 :::* LISTEN tcp6 0 0 192.168.0.103:80 189.209.82.133:18058 SYN_RECV tcp6 0 0 192.168.0.103:80 163.60.156.239:49860 SYN_RECV tcp6 0 0 192.168.0.103:80 132.15.156.83:18041 SYN_RECV tcp6 0 0 192.168.0.103:80 242.35.52.8:63174 SYN_RECV tcp6 0 0 192.168.0.103:80 3.82.206.137:49841 SYN_RECV ... zeeman@target:~$
当再次访问虚拟机target上的Apache时,它是可以访问的。这表明在一定规模的攻击下,SYN Cookie是可以有效防御SYN Flood攻击的。
zeeman@target:~$ curl localhost ... <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> ... zeeman@target:~$
SYN Cache技术指的是,在收到SYN时不急于去分配传输控制块(Transmission Control Block,TCB),而是先回应一个SYN/ACK,并在一个哈希表中保存这种半开连接信息,直到收到正确的回应后ACK再分配传输控制块。在FreeBSD中,对于这种Cache,每个半开连接只需使用160字节,远小于传输控制块所需的736个字节。在发送的SYN/ACK中需要使用一个己方的序列号,这个数字不能被对方猜到,否则对于某些稍微智能一点的TCP Syn Flood Attack软件来说,它们在发送SYN后会发送一个ACK,如果己方的序列号被对方猜测到,就会建立起真正的连接。因此,一般采用加密算法生成难以预测的序列号。
有关SYN Cache的比较详细的描述,可以参考Lemon,J.在2002年发表的“Resisting SYN Flood DoS Attacks with a SYN Cache”。
以FreeBSD为例,SYN Cache相关的参数如下所示。
·hashsize:哈希表的大小。
·bucketlimit:哈希表里每个桶存储的序列号的最大值。
·cachelimit:在syncache中允许存储的序列号的最大值。
·count:当前syncache存储了多少序列号。
除了上面介绍的几种方式外,我们还可以采用iptables来进行限流。iptables的实现机制,可以参考如下例子。
测试环境如下所示。 虚拟化:VirtualBox 5.2 虚拟机:attacker(操作系统:Ubuntu 16.04.5 LTS, 相关软件:hping3, IP地址:192.168.0.107) 虚拟机:target(操作系统:Ubuntu 16.04.5 LTS, 相关软件:Apache2, IP地址:192.168.0.106)
在虚拟机target上,创建如下iptables。
zeeman@target:~$ sudo iptables -N syn_flood zeeman@target:~$ sudo iptables -A INPUT -p tcp --syn -j syn_flood zeeman@target:~$ sudo iptables -A syn_flood -m limit --limit 10/s --limit-burst 100 -j RETURN zeeman@target:~$ sudo iptables -A syn_flood -j DROP zeeman@target:~$ sudo iptables -L Chain INPUT (policy ACCEPT) target prot opt source destination syn_flood tcp -- anywhere anywhere tcp flags:FIN,SYN,RST,ACK/SYN Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination Chain syn_flood (1 references) target prot opt source destination RETURN all -- anywhere anywhere limit: avg 10/sec burst 100 DROP all -- anywhere anywhere zeeman@target:~$
在虚拟机attacker上,利用hping3针对虚拟机target的Apache(80端口)开始发起攻击。
zeeman@attacker:~$ sudo hping3 -S --flood -p 80 --rand-source -V 192.168.0.106 using enp0s3, addr: 192.168.0.107, MTU: 1500 HPING 192.168.0.106 (enp0s3 192.168.0.106): S set, 40 headers + 0 data bytes hping in flood mode, no replies will be shown zeeman@attacker:~$
在虚拟机target上检查iptables的运行结果,发现大量的连接请求都被丢弃了。
zeeman@target:~$ sudo iptables -nvL Chain INPUT (policy ACCEPT 1169 packets, 55070 bytes) pkts bytes target prot opt in out source destination 1882K 75M syn_flood tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp flags:0x17/0x02 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 247 packets, 22688 bytes) pkts bytes target prot opt in out source destination Chain syn_flood (1 references) pkts bytes target prot opt in out source destination 979 39160 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 limit: avg 10/sec burst 100 1881K 75M DROP all -- * * 0.0.0.0/0 0.0.0.0/0 zeeman@attacker:~$
当再次访问虚拟机target上的Apache时,它已经无法访问。简单来讲,iptables的工作原理就是对SYN进行限流,并直接丢弃大量超出阈值的连接请求。
zeeman@target:~$ curl localhost curl: (7) Failed to connect to localhost port 80: Connection timed out zeeman@attacker:~$