DDoS 攻击分类
0x0 前言
Oh please don’t let me die, waiting for you touch~~
0x1 预备
在下面的分析中,将使用神器 Scapy 模拟攻击流量,并使用 tcpdump 截取流量。攻击环境如下:
- Kali 2.0 攻击者,IP 192.168.12.132
- Ubuntu 14.04 服务端,IP 192.168.12.131
使用 scapy 命令进入交互式终端,例如发送一个 ICMP 包:
>>> send( IP(dst='192.168.12.131')/ICMP()/'I am here.', count=1 )
或者向主机循环发送数据包,直到收到终止信号,随机选择目标端口:
>>> send( IP(dst='192.168.12.131')/TCP(dport=RandShort(), flags='S'), loop=1 )
这里推荐一篇极好的文章:Black Hat Python: Infinite possibilities with the Scapy Module
0x2 TCP 三次握手和四次挥手
首先是 TCP 连接建立前的三次握手,这个是老生常谈了。
1)客户端向服务端发送一个 SYN 置位的数据段 // 并初始化一个随机序列号于 seq 字段中
2)服务端回复 SYN / ACK 置位的数据段,连接状态变为 SYN_RCVD // 同样初始化一个序列号,ack 字段为客户端的 seq 序列号加一
3)客户端回复 ACK 置位的数据段 // seq 为最初的序列号自增,ack 为服务端的 seq 序列号加一
自此连接建立完成,双方开始接收或发送数据。
由于 TCP 是全双工的传输协议,所有连接断开时需要进行四次握手,假设客户端为主动请求关闭的一方:
1)客户端发送 FIN / ACK 置位的数据段
2)服务端收到数据,响应 ACK 数据段
3)服务端的接收或发送通道的数据处理完成,向客户端发送 FIN / ACK 数据段
4)客户端收到数据段,发送 ACK 包给服务端,连接完全关闭
TCP 连接断开:
0x3 基于 TCP 握手过程的攻击
DDoS 攻击的类型非常多,针对 TCP 协议的攻击方式也是错综复杂,这里对主要的攻击类型进行分析,笔者将其分为 TCP 握手过程 和 完整握手后 的攻击。
1)TCP SYN Flood
TCP SYN 洪流攻击,是一种主流的 DDoS 攻击类型,效率高,治肾亏不含糖。
1)攻击端向目标主机发送大量 SYN 置位的数据段
2)如果目标主机端口开启,则回复 SYN + ACK 数据段,同时进入 SYN_RECV 状态
3)攻击端收到回复后将其丢弃,使目标主机处于 “半连接” (half-open connection)状态,目标主机会尝试重新发送响应包直至超时
当目标主机的服务连接数超过限制,后续的连接便会被丢弃,造成服务瘫痪。攻击模拟:
1)先使用 iptables 规则将攻击目标的 SYN / ACK 包丢弃,避免本机回复 RST 包终止握手会话:
iptables -A INPUT -s 192.168.12.131 -j DROP
2)发送攻击流量
>>> send( IP(dst='192.168.12.131')/TCP(dport=80, flags='S') )
3)服务器端收到的数据:
首先攻击者先向服务器 80 端口发送 SYN 数据段,服务器返回 SYN / ACK 数据段(标志 [S.] 中的点代表 ACK 置位),该数据包被攻击者直接丢弃。由于攻击者没响应 ACK 包,服务器会尝试重新发送响应数据,造成资源消耗。
2)TCP ACK / SYN + ACK / FIN / RST Flood
这四种攻击分别是:
- TCP ACK Flood
- TCP SYN ACK Flood
- TCP FIN Flood
- TCP RST Flood
无论是三次握手的攻击,还是四次握手的攻击,其原理都是一致的。这种攻击类似于 TCP SYN Flood,虽然效果没有 SYN 洪流药效高,但是通过大片僵尸网络发动攻击同样会造成服务瘫痪。
1)攻击者向目标主机发送 ACK 或 SYN + ACK 或 FIN 置位的数据段
2)目标主机首先检查当前连接是否已建立,如果连接状态无效,则响应 RST 数据段
以 TCP ACK Flood 为例,模拟攻击:
1)发送 ACK 置位的数据:
>>> send( IP(dst='192.168.12.131')/TCP(dport=80, flags='A') )
2)服务器上捕捉的流量:
如图,攻击主机向 80 端口发送 ACK 数据段,服务器先检查队列里是否已经建立了有效的连接,发现为无效连接后,响应 RST 数据段。
可以看到主要针对 服务器对连接状态进行检查的过程 进行攻击,消耗资源。
0x4 完整握手后的 TCP 攻击
这种类型的攻击需要先通过三次握手建立完整的连接,对于攻击者来说这种攻击成本会比较大。因此这种类型的攻击相对于 TCP 握手过程的攻击,更难防范:因为看起来就像一个正常的用户请求。
1)HTTP Flood / HTTPS Flood
HTTP / HTTPS 洪流,也称为 Challenge Collapsar,即 CC 攻击。其原理比较简单,通过僵尸网络向目标发送大量的 HTTP 请求,服务器后端脚本频繁从 后台数据库 或 硬盘 读取数据,造成资源开销。
对于 HTTPS 来说,传输时需要对数据进行加密解密,其消耗的资源会略高于 HTTP 连接。
Collapsar 是绿盟的 DDoS 设备防御设备
2) TCP PSH + ACK Flood
PUSH 标识用于通知 TCP 连接的接受方,不管缓冲区是否已经填满,即刻将数据交付应用层并清空缓冲区,之后发送 ACK 包进行确认。
3)Sockstress
翻阅了维基百科和相关资料,Sockstress 的攻击方式可分为多种:
1. 客户端通过不同源端口向目标主机的指定服务发起大量连接
2. 设置 TCP 接收窗口为零
3. 设置非常小的接收窗口
无论使用 UDP 还是 TCP 进行通信,都需要设置发送/接受缓冲区,而 TCP 实现了流量控制,于是从 TCP 中抽象出来窗口的概念。窗口大小表示可用缓冲区大小,使用窗口的滑动(缓冲区指针移动),关闭(部分缓冲区被填满,并且这些数据还未进行确认,不可覆盖)来表示数据收发操作。
窗口大小在 TCP 握手时进行协商,并且在进行数据传输的时候也会动态变化。
关于更多滑动窗口的细节,可以参阅 《计算机网络 - 自顶向下的学习方法》
当接收方缓冲区填满时,或者网络太过于拥塞,可以发送窗口大小为 0 的数据段告知对方:你这速度比香港记者还快,先等等。
对方收到请求后,会暂停发送数据,同时定时发送一个 Probe 询问接收方是否就绪,直到超时连接丢弃。
Sockstress 便是利用此方式,发送一个窗口大小为零(或者非常小)的数据段,使目标主机持续发送 Probe 包,消耗资源。
首先必须进行三次握手,在最后的 ACK 包搭载 HTTP 请求,并将窗口大小设置为 0:
from scapy.all import *
def sockstress(target, target_port, src_port = 2333):
init_seq = 1000
# send the syn packet
result = sr1( IP(dst=target)/TCP(sport=src_port, dport=target_port, flags='S', seq=init_seq) )
# send the ack packet with the Zero size of window
send( IP(dst=target)/TCP(sport=src_port, dport=target_port, flags='A', seq=init_seq + 1,
ack=result[TCP].seq + 1, window=0)/'GET / HTTP/1.1\r\n\r\n' )
if __name__ == '__main__':
# stress exam
sockstress('192.168.12.131', 80);
发送 payload:
服务器收到停等请求后,每隔一段时间向客户端发送一个 Probe 请求包,直到超时(两次测试后,大概为 187s):
0x5 IP Fragment 攻击
先说分片。
一个 IP 数据包最大能容纳 65535 个字节(包括 IP 头部),但是每次传输的数据大小,取决于链路层的限制。例如以太网帧最大可容纳 1500 字节数据,就是 MTU 啦。
所以当整个 IP 数据报的大小超过 MTU 时,网络层需要对其进行分片,以便在链路层上传输。
IP 协议格式,图片来自 http://www.swiftutors.com/internet-protocol.html
分片涉及三个重要的字段:
1. ID 字段,发生分片时,每个分片都具有相同的 ID,目标主机通过相同 ID 重组数据报
2. Flags,3 bits 大小。最低位保留,第二位 Don't Fragment 标识,分片需要将其置 0,最后为 More Fragment 标识,置位代表后面还有分片,否则为最后一个分片
3. Offset,分片偏移,挑出来详细讲。
首先,下面的描述都是基于以太网帧,即 MTU 为 1500。
分片偏移使用 13 位表示,等于前部分分片累计大小除以 8(不包含 IP 头大小)。
例如发送总大小为 4000 字节的数据包,假设 IP 头固定为 20 字节,则每个分片最大能携带 1480 字节数据,需要 (4000 - 20) / 1480 =~ 2.6 即三个分片。第一个分片偏移为 0/8 = 0,接着为 1480/8 = 185,最后为 2960/8 = 370。
为啥分片偏移还要除以 8?
因为偏移用 13 位来表示,最大值为 8191。按照普通编号,超过 6 个分片时,其偏移为 1480 * 6 = 8880。明显,分片偏移无法容纳这么大的数字。
再谈攻击。
To be continue…