使用packetdrill模拟Challenge-ACK

本文阅读量 Posted by Kird on 2020-06-24

背景

packetdrill 在 2013 年开源,在 Google 内部久经考验,Google 用它发现了 10 余个 Linux 内核 bug,同时用测试驱动开发的方式开发新的网络特性和进行回归测试,确保新功能的添加不影响网络协议栈的可用性。
github地址
使用参考文章
Challenge-ACK定义:
Linux 内核对于收到的乱序 SYN 报文,会回复一个携带了正确序列号和确认号的 ACK 报文。
本文通过packetdrill模拟ESTAB状态下服务端收到乱序SYN后,发送challenge-ack的现象。

测试

packetdrill程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
--tolerance_usecs=100000
//新建socket并监听
0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, TCP_NODELAY, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 0
//3次握手
+0 < S 0:0(0) win 32792 <mss 1000,nop,wscale 7>
+0 > S. 0:0(0) ack 1 <mss 1460,nop,wscale 7>
+0.1 < . 1:1(0) ack 1 win 257
0.000...0.200 accept(3, ..., ...) = 4
//服务端发送2000字节数据
//+0.300 write(4, ..., 2000) = 2000
//+0 > P. 1:2001(2000) ack 1
//分两次发送,第一次1:1001,1000字节,需要关闭nagle
+0.4 > . 1:1001(1000) ack 1
//客户端确认
+0 < . 1:1(0) ack 1001 win 1000
//第二次发送1000字节
+0 > P. 1001:2001(1000) ack 1
//发送确认
+0 < . 1:1(0) ack 2001 win 1000
//模拟发送攻击者构造的SYN
+0.1 < S 2:100(98) win 32792
+0.5 < S 3:100(97) win 32792
+1 < S 4:100(96) win 32792
+0 `sleep 1000`

3个SYN分别间隔0.1s,+0.5s,+1s发送。

tcpdump查看过程

抓包命令:tcpdump -i any -n host 192.0.2.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
14:43:40.546883 IP 192.0.2.1.42451 > 192.168.193.131.8080: Flags [S], seq 0, win 32792, options [mss 1000,nop,wscale 7], length 0
14:43:40.546909 IP 192.168.193.131.8080 > 192.0.2.1.42451: Flags [S.], seq 2514904743, ack 1, win 64240, options [mss 1460,nop,wscale 7], length 0
14:43:40.647084 IP 192.0.2.1.42451 > 192.168.193.131.8080: Flags [.], ack 1, win 257, length 0
14:43:40.947881 IP 192.168.193.131.8080 > 192.0.2.1.42451: Flags [P.], seq 1:2001, ack 1, win 502, length 2000: HTTP
14:43:41.262097 IP 192.168.193.131.8080 > 192.0.2.1.42451: Flags [.], seq 1:1001, ack 1, win 502, length 1000: HTTP
14:43:41.262221 IP 192.0.2.1.42451 > 192.168.193.131.8080: Flags [.], ack 1001, win 1000, length 0
14:43:41.262243 IP 192.168.193.131.8080 > 192.0.2.1.42451: Flags [P.], seq 1001:2001, ack 1, win 502, length 1000: HTTP
14:43:41.262295 IP 192.0.2.1.42451 > 192.168.193.131.8080: Flags [.], ack 2001, win 1000, length 0
14:43:41.362415 IP 192.0.2.1.42451 > 192.168.193.131.8080: Flags [S], seq 2:100, win 32792, length 98: HTTP
14:43:41.362450 IP 192.168.193.131.8080 > 192.0.2.1.42451: Flags [.], ack 1, win 502, length 0
14:43:42.363356 IP 192.0.2.1.42451 > 192.168.193.131.8080: Flags [S], seq 3:100, win 32792, length 97: HTTP
14:43:42.363402 IP 192.168.193.131.8080 > 192.0.2.1.42451: Flags [.], ack 1, win 502, length 0
14:43:43.363009 IP 192.0.2.1.42451 > 192.168.193.131.8080: Flags [S], seq 4:100, win 32792, length 96: HTTP
14:43:43.363047 IP 192.168.193.131.8080 > 192.0.2.1.42451: Flags [.], ack 1, win 502, length 0

可以看到在ESTAB状态,服务端对每一个SYN都回复了一个ACK(最后6个报文)。
如果攻击者利用这一特性,可造成服务器发送大量的ack消耗资源,可调小内核参数net.ipv4.tcp_challenge_ack_limit,该参数定义1s之内发送挑战应答的次数,默认为1000,1s内只发送1000个ack,之后不再发送,需要等下一秒内重新恢复1000计数,本次测试中,如果将该参数调整到1后,因为3个SYN分别间隔0.1s,+0.5s,+1s发送,前两个SYN应该只有第一个SYN收到ack应答,测试结果抓包如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14:52:16.067101 IP 192.0.2.1.53531 > 192.168.175.120.8080: Flags [S], seq 0, win 32792, options [mss 1000,nop,wscale 7], length 0
14:52:16.067132 IP 192.168.175.120.8080 > 192.0.2.1.53531: Flags [S.], seq 686321228, ack 1, win 64240, options [mss 1460,nop,wscale 7], length 0
14:52:16.167397 IP 192.0.2.1.53531 > 192.168.175.120.8080: Flags [.], ack 1, win 257, length 0
14:52:16.467954 IP 192.168.175.120.8080 > 192.0.2.1.53531: Flags [P.], seq 1:2001, ack 1, win 502, length 2000: HTTP
14:52:16.782085 IP 192.168.175.120.8080 > 192.0.2.1.53531: Flags [.], seq 1:1001, ack 1, win 502, length 1000: HTTP
14:52:16.782199 IP 192.0.2.1.53531 > 192.168.175.120.8080: Flags [.], ack 1001, win 1000, length 0
14:52:16.782219 IP 192.168.175.120.8080 > 192.0.2.1.53531: Flags [P.], seq 1001:2001, ack 1, win 502, length 1000: HTTP
14:52:16.782266 IP 192.0.2.1.53531 > 192.168.175.120.8080: Flags [.], ack 2001, win 1000, length 0
14:52:16.883057 IP 192.0.2.1.53531 > 192.168.175.120.8080: Flags [S], seq 2:100, win 32792, length 98: HTTP
14:52:16.883097 IP 192.168.175.120.8080 > 192.0.2.1.53531: Flags [.], ack 1, win 502, length 0
14:52:17.383434 IP 192.0.2.1.53531 > 192.168.175.120.8080: Flags [S], seq 3:100, win 32792, length 97: HTTP
14:52:18.383179 IP 192.0.2.1.53531 > 192.168.175.120.8080: Flags [S], seq 4:100, win 32792, length 96: HTTP
14:52:18.383224 IP 192.168.175.120.8080 > 192.0.2.1.53531: Flags [.], ack 1, win 502, length 0

可以看到倒数第三个数据包(第二个SYN),服务器并没有响应挑战应答。

内核相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th)
{
// seq 不在窗口内
/* Step 1: check sequence number */
if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
// RST 标记没有设置
if (!th->rst) {
if (th->syn)
goto syn_challenge;
}
goto discard;
}

/* step 4: Check for a SYN。 RFC 5961 4.2 : Send a challenge ack */
if (th->syn) {
syn_challenge: // 处理 SYN Challenge 的情况
tcp_send_challenge_ack(sk, skb); //
goto discard;
}


支付宝打赏 微信打赏

赞赏支持一下