TCP挑战ACK报文限速

 

PROC文件tcp_challenge_ack_limit控制每秒钟发送挑战ACK报文的数量。避免遭受Blind In-Window Attacks,包括reset,sync或者数据注入攻击等,详解RFC5961。

初始化

在TCP协议初始化函数tcp_sk_init中赋值为1000。通过PROC文件tcp_challenge_ack_limit可查看此值,并可进行修改。

static int __net_init tcp_sk_init(struct net *net)
{
    /* rfc5961 challenge ack rate limiting */
    net->ipv4.sysctl_tcp_challenge_ack_limit = 1000;
}
$ cat /proc/sys/net/ipv4/tcp_challenge_ack_limit
1000

挑战ACK发送控制
 

核心函数tcp_send_challenge_ack如下。如果挑战ACK报文要能够发送,必须首先满足tcp_invalid_ratelimit变量定义的时间间隔,默认为HZ的一半即500毫秒。详情参见:https://blog.csdn.net/sinat_20184565/article/details/89481607。

算法如下:在首次进入此函数时,挑战时间戳和挑战次数都为空,获取到当前的时间(now的单位秒),此时now必定不等于挑战时间戳,将挑战次数初始化为tcp_challenge_ack_limit设定值的一半,与零至tcp_challenge_ack_limit值之间的随机值的和,并且初始化挑战时间戳。之后,如果再次进入函数时,当前时间还在同一秒内,判断挑战次数是否为零,不成立递减挑战次数,发送挑战ACK报文。

如果再次进入函数时,当前时间已经进入下一秒,重新初始化挑战次数challenge_count和挑战时间戳challenge_timestamp。在同一秒钟内,已经发送了超过挑战次数的ACK报文后,不在发送挑战ACK报文。

/* RFC 5961 7 [ACK Throttling] */
static void tcp_send_challenge_ack(struct sock *sk, const struct sk_buff *skb)
{
    static u32 challenge_timestamp;
    static unsigned int challenge_count;

    /* First check our per-socket dupack rate limit. */
    if (__tcp_oow_rate_limited(net, LINUX_MIB_TCPACKSKIPPEDCHALLENGE, &tp->last_oow_ack_time))
        return;

    /* Then check host-wide RFC 5961 rate limit. */
    now = jiffies / HZ;
    if (now != challenge_timestamp) {
        u32 ack_limit = net->ipv4.sysctl_tcp_challenge_ack_limit;
        u32 half = (ack_limit + 1) >> 1;

        challenge_timestamp = now;
        WRITE_ONCE(challenge_count, half + prandom_u32_max(ack_limit));
    }
    count = READ_ONCE(challenge_count);
    if (count > 0) {
        WRITE_ONCE(challenge_count, count - 1);
        NET_INC_STATS(net, LINUX_MIB_TCPCHALLENGEACK);
        tcp_send_ack(sk);
    }
}

 

数据注入攻击


如果接收报文的确认序号小于本地套接口待确认序号,表明为一个已经确认过的序号,并且其确认序号在套接口当前待确认序号减去本地发送窗口之前,内核认为此报文很可能并非对端发送,即其为攻击者构造出来的报文,合法的报文确认序号ACK的范围:((SND.UNA - MAX.SND.WND) <= SEG.ACK <= SND.NXT)。否则,不在此范围内认为是盲数据注入攻击Blind Data Injection Attack。但是,有可能并非攻击者发送,而是来自对端的报文,此时回复挑战ACK报文,使对端有机会修正其确认序号ACK。

按照RFC5961的描述,为应对数据注入攻击,进一步缩小合法确认序号ACK的范围,要求报文中的确认序号大于套接口第一个待确认序号。合法的报文确认序号范围:(SND.UNA <= SEG.ACK <= SND.NXT)。内核不处理无效确认序号的报文,避免注入非法数据。

static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{
    u32 prior_snd_una = tp->snd_una;

    /* If the ack is older than previous acks then we can probably ignore it. */
    if (before(ack, prior_snd_una)) {
        /* RFC 5961 5.2 [Blind Data Injection Attack].[Mitigation] */
        if (before(ack, prior_snd_una - tp->max_window)) {
            if (!(flag & FLAG_NO_CHALLENGE_ACK))
                tcp_send_challenge_ack(sk, skb);
            return -1;
        }
        goto old_ack;
    }
    if (after(ack, tp->snd_nxt))
        goto invalid_ack;
	
old_ack:

    SOCK_DEBUG(sk, "Ack %u before %u:%u\n", ack, tp->snd_una, tp->snd_nxt);
    return 0;
}

 

复位RST攻击

如下的TCP报文检查函数tcp_validate_incoming。
为应对攻击者伪造的RST报文,对于TCP头部标志设置了reset位的报文,仅当其序号等于本端套接口待接收的序号时(增加攻击者猜测的序号的难度),或者tcp_reset_check函数结果为真时(其判读RST是否紧随之前的一个FIN报文),本端才可复位此连接。另外一种情况是,当启用SACK时,仅当报文序号等于所有SACK块中最大的数据序号时,本端可复位此连接。

以上条件都不成立,内核认为不合法的RST报文或者为恶意的RST攻击,回应挑战ACK报文,以使得发送不合法报文的对端得以恢复。

static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, int syn_inerr)
{
    /* Step 2: check RST bit */
    if (th->rst) {
        if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt || tcp_reset_check(sk, skb)) {
            rst_seq_match = true;
        } else if (tcp_is_sack(tp) && tp->rx_opt.num_sacks > 0) {
            struct tcp_sack_block *sp = &tp->selective_acks[0];
            int max_sack = sp[0].end_seq;

            for (this_sack = 1; this_sack < tp->rx_opt.num_sacks; ++this_sack) {
                max_sack = after(sp[this_sack].end_seq, max_sack) ? sp[this_sack].end_seq : max_sack;
            }
            if (TCP_SKB_CB(skb)->seq == max_sack)
                rst_seq_match = true;
        }

        if (rst_seq_match)
            tcp_reset(sk);
        else {
            /* Disable TFO if RST is out-of-order and no data has been received for current active TFO socket */
            if (tp->syn_fastopen && !tp->data_segs_in && sk->sk_state == TCP_ESTABLISHED)
                tcp_fastopen_active_disable(sk);
            tcp_send_challenge_ack(sk, skb);
        }
        goto discard;
    }
}

如果RST报文的序号不等于套接口的待接收序号,当时等于待接收序号减一,并且套接口之前接收到了FIN报文,对于主动关闭端,接收到FIN报文之后将进入TCPF_CLOSING状态。对于被动关闭端,接收到FIN报文之后,进入TCPF_CLOSE_WAIT状态,在套接口close之后,转换到TCPF_LAST_ACK状态。

根据内核的注释,对于Mac OSX系统中意外结束的TCP连接(Ctrl+C),OSX将接连发送FIN和RST报文,并且RST报文的序号等于之前的FIN报文序号。

static bool tcp_reset_check(const struct sock *sk, const struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);
    return unlikely(TCP_SKB_CB(skb)->seq == (tp->rcv_nxt - 1) &&
            (1 << sk->sk_state) & (TCPF_CLOSE_WAIT | TCPF_LAST_ACK | TCPF_CLOSING));
}

 

SYN攻击

对于处在连接阶段的TCP连接,未避免攻击者伪造SYN报文导致接收端误以为对端连接已关闭,需要重新建立而导致的RST报文发送问题,内核改为对接收到的SYN报文不管其序号是否合法,仅回复挑战ACK报文,内容如下:<SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>。如果对端确实要重新开始新的连接,在接收到挑战ACK报文之后,可根据其中的确认序号构造一个合法的RST报文先关闭之前的连接。如此处理,攻击者伪造的SYN报文将不能够复位存在的链接了。

static bool tcp_validate_incoming(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, int syn_inerr)
{
    /* Step 1: check sequence number */
    if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
        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:
        tcp_send_challenge_ack(sk, skb);
        goto discard;
    }
}

 

以上处理,有一个小的弊端,当对端意外重启之后,如果使用相同的TCP的IP地址和端口号重新发起连接,发送SYN报文,并且,其初始序号选择了RCV.NXT-1的值,加入本端的连接还在,等接收到此SYN报文时,将回复挑战ACK报文,其确认序号为RCV.NXT,但是当对端接收到此挑战ACK报文,因为其正好确认了之前发送的SYN报文,对端并不会发送RST报文结束重启之前的连接。导致新连接一直建立不起来,要等到SYN报文超时。不过这属于非常极端情况。

 

内核版本 4.15

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值