精华内容
下载资源
问答
  • SYN Cookie

    2020-03-25 11:49:19
    SYN Cookie算法 SYN Cookie是对TCP服务器端的三次握手做一些修改,专门用来防范SYN Flood攻击的一种手段。它的原理是,在TCP服务器接收到TCP SYN包并返回TCP SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN...

    SYN Cookie算法

    SYN Cookie是对TCP服务器端的三次握手做一些修改,专门用来防范SYN Flood攻击的一种手段。它的原理是,在TCP服务器接收到TCP SYN包并返回TCP SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值,这个cookie作为将要返回的SYN ACK包的初始序列号。当客户端返回一个ACK包时,根据包头信息计算cookie,与返回的确认序列(初始序列号+1)进行对比,如果相同,则是一个正常链接,然后分配资源,建立连接。实现的关键在于cookie的计算,cookie的计算应该包含本次链接的状态信息,使攻击者不能伪造。
    实现
    发起一个TCP连接时,客户端将一个TCP SYN包发送给服务器。作为响应,服务器将TCP SYN+ACK包返回给客户端。此数据包有一个序号,它被TCP用来重新组装数据流。根据TCP规范,由端点发送的第一个序号可以是由该端点决定的任何值。SYN Cookies是根据以下规则的初始序号

    • 令t为一个缓慢递增的时间戳(通常time()>>6,提供64秒的分辨率);

    • 令m为服务器会在SYN队列条目中储存的最大分段大小(Maximum segment size);

    • 令s为一个加密散列函数对服务器和客户端各自的IP地址和端口号以及t进行运算的 结果。返回得到的数值s必须是一个24位值。

    初始TCP序号,也就是所谓的SYN cookie,按照如下算法得到:

    • 头五位:t mod 32;
    • 中三位:m编码后的数值;
    • 末24位:s本身;

    根据TCP规范,当客户端发回TCP ACK包给服务器以响应服务器的SYN+ACK包时,客户端必须使用由服务器发送的初始序号加1作为数据包中的确认号。服务器接着从确认号中减去1以便还原向客户端发送的原始SYN Cookie
    接下来服务器进行一下检查:

    • 配合现在时间t来检查连接是否过期

    • 重新计算s来确认这是不是一个有效的SYN Cookie

    • 从3位编码中解码m,以便之后用来重建SYN队列条目。在此之后,连接照常进行

    展开全文
  • 到目前为止,能够有效防范SYN Flood攻击的手段并不多,而SYN Cookie就是其中最著名的一种。SYN Cookie原理由D. J. Bernstain和 Eric Schenk发明。在很多操作系统上都有各种各样的实现。其中包括Linux。本文就分别...
  • SYN Cookie的原理和实现

    万次阅读 多人点赞 2014-01-06 16:56:15
    SYN Cookie   判断是否使用SYN Cookie。如果SYN Cookie功能有编译进内核(CONFIG_SYN_COOKIE),且选项 tcp_syncookie不为0,那么可使用SYN Cookie。.../* Return true if a syncookie should be sen

    本文主要内容:SYN Cookie的原理,以及它的内核实现。

    内核版本:3.6

    Author:zhangskd @ csdn blog

     

    SYN Flood

     

    下面这段介绍引用自[1].

    SYN Flood是一种非常危险而常见的Dos攻击方式。到目前为止,能够有效防范SYN Flood攻击的手段并不多,

    SYN Cookie就是其中最著名的一种。

     

    SYN Flood攻击是一种典型的拒绝服务(Denial of Service)攻击。所谓的拒绝服务攻击就是通过进行攻击,使受害主机或

    网络不能提供良好的服务,从而间接达到攻击的目的。

    SYN Flood攻击利用的是IPv4中TCP协议的三次握手(Three-Way Handshake)过程进行的攻击。

    TCP服务器收到TCP SYN request包时,在发送TCP SYN + ACK包回客户机前,TCP服务器要先分配好一个数据区专门

    服务于这个即将形成的TCP连接。一般把收到SYN包而还未收到ACK包时的连接状态称为半打开连接(Half-open Connection)。

    在最常见的SYN Flood攻击中,攻击者在短时间内发送大量的TCP SYN包给受害者。受害者(服务器)为每个TCP SYN包分配

    一个特定的数据区,只要这些SYN包具有不同的源地址(攻击者很容易伪造)。这将给TCP服务器造成很大的系统负担,最终

    导致系统不能正常工作。

     

    SYN Cookie

     

    SYN Cookie原理由D.J. Bernstain和Eric Schenk提出。

    SYN Cookie是对TCP服务器端的三次握手做一些修改,专门用来防范SYN Flood攻击的一种手段。它的原理是,在TCP服务器

    接收到TCP SYN包并返回TCP SYN + ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。这个

    cookie作为将要返回的SYN ACK包的初始序列号。当客户端返回一个ACK包时,根据包头信息计算cookie,与返回的确认序列

    号(初始序列号 + 1)进行对比,如果相同,则是一个正常连接,然后,分配资源,建立连接。

     

    实现的关键在于cookie的计算,cookie的计算应该包含本次连接的状态信息,使攻击者不能伪造。

    cookie的计算:

    服务器收到一个SYN包,计算一个消息摘要mac。

    mac = MAC(A, k);

    MAC是密码学中的一个消息认证码函数,也就是满足某种安全性质的带密钥的hash函数,它能够提供cookie计算中需要的安全性。

    在Linux实现中,MAC函数为SHA1。

    A = SOURCE_IP || SOURCE_PORT || DST_IP || DST_PORT || t || MSSIND

    k为服务器独有的密钥,实际上是一组随机数。

    t为系统启动时间,每60秒加1。

    MSSIND为MSS对应的索引。

     

    实现

     

    (1)启用条件

    判断是否使用SYN Cookie。如果SYN Cookie功能有编译进内核(CONFIG_SYN_COOKIE),且选项

    tcp_syncookies不为0,那么可使用SYN Cookie。同时设置SYN Flood标志(listen_opt->synflood_warned)。

    /* Return true if a syncookie should be sent. */
    bool tcp_syn_flood_action(struct sock *sk, const struct sk_buff *skb, const char *proto)
    {
        const char *msg = "Dropping request";
        bool want_cookie = false;
        struct listen_sock *lopt;
    
    #ifdef CONFIG_SYN_COOKIE
        if (sysctl_tcp_syncookies) { /* 如果允许使用SYN Cookie */
            msg = "Sending cookies";
            want_cookie = true;
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPREQQFULLDOCOOKIES);
        } else
    #endif
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPREQQFULLDROP);
    
        lopt = inet_csk(sk)->icsk_accept_queue.listen_opt; /* 半连接队列 */
    
        if (! lopt->synflood_warned) {
            lopt->synflood_warned = 1; /* 设置SYN Flood标志 */
            pr_info("%s: Possible SYN flooding on port %d. %s.  Check SNMP counters.\n",
                           proto, ntohs(tcp_hdr(skb)->dest), msg);
        }
    
        return want_cookie;
    }
    

     

    (2)生成cookie

    计算SYN Cookie的值。

    函数调用路径:

    tcp_v4_conn_request

            |--> cookie_v4_init_sequence

                              |--> secure_tcp_syn_cookie

    /* Generate a syncookie. mssp points to the mss, which is returned rounded down to the
     * value encoded in the cookie.
     */
    
    __u32 cookie_v4_init_sequence(struct sock *sk, struct sk_buff *skb, __u16 *mssp)
    {
        const struct iphdr *iph = ip_hdr(skb);
        const struct tcphdr *th = tcp_hdr(skb);
        int mssind; /* mss index */
        const __u16 mss = *mssp;
    
        tcp_synq_overflow(sk); /* 记录半连接队列溢出的最近时间 */
    
        for (mssind = ARRAY_SIZE(msstab) - 1; mssind; mssind--)
            if (mss >= msstab[mssind])
                break;
        *mssp = msstab[mssind];
    
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESSENT);
    
        return secure_tcp_syn_cookie(iph->saddr, iph->daddr, th->source, th->dest, ntohl(th->seq),
                          jiffies / (HZ * 60), mssind); /* 计算SYN Cookie的具体值 */
    }
    /* syncookie: remember time of last synqueue overflow */
    static inline void tcp_synq_overflow(struct sock *sk)
    {
        tcp_sk(sk)->rx_opt.ts_recent_stamp = jiffies;
    }
    
    /* 
     * MSS Values are taken from the 2009 paper
     * 'Measuring TCP Maximum Segment Size' by S. Alcock and R. Nelson:
     * - values 1440 to 1460 accounted for 80% of observed mss values
     * - values outside the 536-1460 range are rare (<0.2%).
     *
     * Table must be sorted.
     */
    static __u16 const msstab[] = {
        64,
        512,
        536,
        1024,
        1440,
        1460,
        4312,
        8960,
    };
    static __u32 secure_tcp_syn_cookie(__be32 saddr, __be32 daddr, __be16 sport, __be16 dport,
                                       __u32 sseq, __u32 count, __u32 data)
    {
        /* Compute the secure sequence number.
         * The output should be:
         * HASH(sec1, saddr, sport, daddr, dport, sec1) + sseq + (count * 2^24) +
         *     (HASH(sec2, saddr, sport, daddr, dport, count, sec2) % 2^24).
         * Where sseq is their sequence number and count increases every minute by 1.
         * As an extra hack, we add a small "data" value that encodes the MSS into the second hash value.
         */
        return (cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq + (count << COOKIEBITS) +
                  ((cookie_hash(saddr, daddr, sport, dport, count, 1) + data) & COOKIEMASK));
    
    }
    
    #define COOKIEBITS 24 /* Upper bits store count */
    #define COOKIEMASK (((__u32) 1 << COOKIEBITS) - 1)
    #define SHA_DIGEST_WORDS 5
    #define SHA_WORKSPACE_WORDS 16
    

    服务器的密钥、SHA1计算。

    __u32 syncookie_secret[2] [16 - 4 + SHA_DIGEST_WORDS];
    
    static __init int init_syncookies(void)
    {
        get_random_bytes(syncookie_secret, sizeof(syncookie_secret));
        return 0;
    }
    
    static DEFINE_PER_CPU(__u32 [16 + 5 + SHA_WORKSPACE_WORDS], ipv4_cookie_scratch);
    
    static u32 cookie_hash(__be32 saddr, _be32 daddr, __be16 sport, __be16 dport, u32 count, int c)
    {
        __u32 *tmp = __get_cpu_var(ipv4_cookie_scratch);
    
        memcpy(tmp + 4, syncookie_secret[c], sizeof(syncookie_secret[c])); /* c取值为0、1 */
        tmp[0] = (__force u32) saddr;
        tmp[1] = (__force u32) daddr;
        tmp[2] = ((__force u32) sport << 16) + (__force u32) dport;
        tmp[3] = count;
    
        sha_transform(tmp + 16, (__u8 *)tmp, tmp + 16 + 5); /* generate a 160-bit digest from 512-bit block */
        return tmp[17];
    }

    SHA1

    安全哈希算法(Secure HASH Algorithm)主要适用于数字签名。

    对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来

    验证数据的完整性。在传输的过程中,数据可能会发生变化,那么这时候就会产生不同的消息摘要。

    SHA1有如下特性:

    1. 不可以从消息摘要中复原信息。

    2. 两个不同的消息不会产生同样的消息摘要。

    在Git中,也使用SHA1来标识每一次提交。

    /* sha_transform - single block SHA1 transform
     * @digest: 160 bit digest to update
     * @data: 512 bits of data to hash
     * @array: 16 words of workspace (see note)
     *
     * This function generates a SHA1 digest for a single 512-bit block.
     * /
    void sha_transform(__u32 *digest, const char *data, __u32 *array) {}
    

     

    (3)保存TCP选项信息

    tcp_v4_send_synack

            |--> tcp_make_synack

                           |--> cookie_init_timestamp

    如果SYNACK段使用SYN Cookie,并且使用时间戳选项,则把TCP选项信息保存在SYNACK段中tsval的低6位。

    /* When syncookies are in effect and tcp timestamps are enabled we encode tcp options
     * in the lower bits of the timestamp value that will be sent in the syn-ack.
     * Since subsequent timestamps use the normal tcp_time_stamp value, we must make
     * sure that the resulting initial timestamp is <= tcp_time_stamp.
     */
    __u32 cookie_init_timestamp(struct request_sock *req)
    {
        struct inet_request_sock *ireq;
        u32 ts, ts_now = tcp_time_stamp;
        u32 options = 0;
        ireq = inet_rsk(req);
    
        options = ireq->wscale_ok ? ireq->snd_wscale : 0xf;
        options |= ireq->sack_ok << 4;
        options |= ireq->ecn_ok << 5;
    
        ts = ts_now & ~TSMASK;
        ts |= options;
    
        if (ts > ts_now) {
            ts >>= TSBITS;
            ts--;
            ts <<= TSBITS;
            ts |= options;
        }
        return ts;
    }
    
    #define TSBITS 6
    #define TSMASK (((__u32) 1 << TSBITS) - 1)
    


    (4)验证cookie

    函数调用路径:

    tcp_v4_hnd_req

            |--> cookie_v4_check

                          |--> cookie_check

                                           |--> check_tcp_syn_cookie

     

    SYN Cookie的设计非常巧妙, 我们来看看它是怎么验证的。

    首先,把ACK包的ack_seq - 1,得到原来计算的cookie。把ACK包的seq - 1,得到SYN段的seq。

    cookie的计算公式为:

    cookie = cookie_hash(saddr, daddr, sport, dport, 0, 0) + seq +

                    (t1 << 24) + (cookie_hash(saddr, daddr, sport, dport, t1, 1) + mssind) % 24;

    t1为服务器发送SYN Cookie的时间,单位为分钟,保留在高12位。

    mssind为MSS的索引(0 - 7),保留在低24位。

     

    现在可以反过来求t1:

    t1 = (cookie - cookie_hash(saddr, daddr, sport, dport, 0, 0) - seq) >> 24; /* 高12位表示时间 */

    t2为收到ACK的时间,t2 - t1 < 4分钟,才是合法的。也就是说ACK必须在4分钟内到达才行。

     

    验证完时间后,还需验证mssind:

    cookie -= (cookie_hash(saddr, daddr, sport, dport, 0, 0) - seq);

    mssind = (cookie - cookie_hash(saddr, daddr, sport, dport, t1, 1)) % 24; /* 低24位 */

    mssind < 8,才是合法的。

     

    如果t1和mssind都是合法的,则认为此ACK是合法的,可以直接完成三次握手。

    /* Check if a ack sequence number is a valid syncookie.
     * Return the decoded mss if it is, or 0 if not.
     */
    
    static inline int cookie_check(struct sk_buff *skb, __u32 cookie)
    {
        const struct iphdr *iph = ip_hdr(skb);
        const struct tcphdr *th = tcp_hdr(skb);
        __u32 seq = ntohl(th->seq) - 1; /* SYN的序号 */
    
        __u32 mssind = check_tcp_syn_cookie(cookie, iph->saddr, iph->daddr, th->source, th->dest,
                              seq, jiffies / (HZ * 60), COUNTER_TRIES);
    
        /* 如果不合法则返回0 */
        return mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0;
    }
    
    /* 使用SYN Cookie时,ACK超过了这个时间到达,会被认为不合法。*/
    /* This (misnamed) value is the age of syncookie which is permitted.
     * Its ideal value should be dependent on TCP_TIMEOUT_INIT and sysctl_tcp_retries1.
     * It's a rather complicated formula (exponential backoff) to compute at runtime so it's
     * currently hardcoded here.
     */
    #define COUNTER_TRIES 4 /* 4分钟 */
    
    static __u32 check_tcp_syn_cookie(__u32 cookie, __be32 saddr, __be32 daddr, __be16 sport,
                __be16 dport, __u32 sseq, __u32 count, __u32 maxdiff)
    {
        __u32 diff;
    
        /* Strip away the layers from the cookie, 剥去固定值的部分 */
        cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;
    
        /* Cookie is now reduced to (count * 2^24) + (hash % 2^24) */
        diff = (count - (cookie >> COOKIEBITS)) & ((__u32) -1 >> COOKIEBITS); /* 高12位是时间,单位为分钟 */
        if (diff >= maxdiff)
            return (__u32)-1;
    
        /* Leaving the data behind,返回的是原来的data,即mssind */
        return (cookie - cookie_hash(saddr, daddr, sport, dport, count - diff, 1)) & COOKIEMASK;
    }
    

     

    (5)建立连接

    接收到ACK后,SYN Cookie的处理函数为cookie_v4_check()。

    首先要验证cookie是否合法。

    如果cookie是不合法的,返回监听sk,会导致之后发送一个RST给客户端。

    如果cookie是合法的,则创建和初始化连接请求块。接着为新的连接创建和初始化一个新的传输控制块,

    把它和连接请求块关联起来,最后把该连接请求块链入全连接队列中,等待accept()。

     

    时间戳对SYN Cookie有着重要的意义,如果不支持时间戳选项,则通过SYN Cookie建立的连接就会

    不支持大多数TCP选项。

    struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb, struct ip_options *opt)
    {
        struct tcp_options_received tcp_opt;
        const u8 *hash_location;
        struct inet_request_sock *ireq;
        struct tcp_request_sock *treq;
        struct tcp_sock *tp = tcp_sk(sk);
        const struct tcphdr *th = tcp_hdr(skb);
        __u32 cookie = ntohl(th->ack_seq) - 1;
        struct sock *ret = sk;
        struct request_sock *req;
        int mss;
        struct rtable *rt;
        __u8 rcv_wscale;
        bool ecn_ok = false;
        struct flowi4 fl4;
    
        if (! sysctl_tcp_syncookies || ! th->ack || th->rst)
            goto out;
    
        /* 验证cookie的合法性,必须同时符合:
         * 1. 最近3s内有发生半连接队列溢出。
         * 2. 通过cookie反算的t1和mssind是合法的。
         */
        if (tcp_synq_no_recent_overflow(sk) || (mss = cookie_check(skb, cookie)) == 0) {
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESFAILED);
            goto out;
        }
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESRECV);
    
        /* check for timestamp cookie support */
        memset(&tcp_opt, 0, sizeof(tcp_opt));
    
        /* 全面解析TCP选项,并保存到tcp_opt中 */
        tcp_parse_options(skb, &tcp_opt, &hash_location, 0, NULL);
    
        /* 如果有使用时间戳选项,则从ACK的tsecr中提取选项信息 */
        if (! cookie_check_timestamp(&tcp_opt, &ecn_ok))
            goto out;
    
        ret = NULL;
        /* 从缓存块中分配一个request_sock实例,指定此实例的操作函数集为tcp_request_sock_ops */
        req = inet_reqsk_alloc(&tcp_request_sock_ops);
        if (! req)
            goto out;
    
        ireq = inet_rsk(req);
        treq = tcp_rsk(req);
        treq->rcv_isn = ntohl(th->seq) - 1; /* 客户端的初始序列号 */
        treq->snt_isn = cookie; /* 本端的初始序列号 */
        req->mss = mss; /* 客户端通告的MSS,通过解析cookie获得 */
        ireq->loc_port = th->dest; /* 本端端口 */
        ireq->rmt_port = th->source; /* 客户端端口 */
        ireq->loc_addr = ip_hdr(skb)->daddr; /* 本端IP */
        ireq->rmt_addr = ip_hdr(skb)->saddr; /* 客户端IP */
        ireq->ecn_ok = ecn_ok; /* ECN选项,通过TS编码获得 */
        ireq->snd_wscale = tcp_opt.snd_wscale; /* 客户端窗口扩大因子,通过TS编码获得 */
        ireq->sack_ok = tcp_opt.sack_ok; /* SACK允许选项,通过TS编码获得 */
        ireq->wscale_ok = tcp_opt.wscale_ok; /* 窗口扩大选项,通过TS编码获得 */
        ireq->tstamp_ok = tcp_opt.saw_tstamp; /* 时间戳选项,通过观察ACK段有无携带时间戳 */
        req->ts_recent = tcp_opt.saw_tstamp ? tcp_opt.rcv_tsval : 0; /* 本端下个发送段的时间戳回显值 */
        treq->snt_synack = tcp_opt.saw_tstamp ? tcp_opt.rcv_tsecr : 0; /* 本端发送SYNACK段的时刻 */
    
        /* We throwed the options of the initial SYN away, so we hope the ACK carries the same options
         * again (see RFC1122 4.2.3.8)
         * 通过ACK段,获取IP选项。
         */
        if (opt && opt->optlen) {
            int opt_size = sizeof(struct ip_options_rcu) + opt->optlen;
            ireq->opt = kmalloc(opt_size, GFP_ATOMIC);
    
            if (ireq->opt != NULL && ip_options_echo(&ireq->opt->opt, skb)) {
                kfree(ireq->opt);
                ireq->opt = NULL;
            }
        }
    
        /* SELinux相关 */
        if (security_inet_conn_request(sk, skb, req)) {
            reqsk_free(req);
            goto out;
        }
    
        req->expires = 0UL; /* SYNACK的超时时间 */
        req->retrans = 0; /* SYNACK的重传次数 */
    
        /* We need to lookup the route here to get at the correct window size.
         * We should better make sure that the window size hasn't changed since we
         * received the original syn, but I see no easy way to do this.
         * 查找路由缓存。
         */
        flowi4_init_output(&fl4, 0, sk->sk_mark, RT_CONN_FLAGS(sk), RT_SCOPE_UNIVERSE,
            IPPROTO_TCP, inet_sk_flowi_flags(sk), (opt && opt->srr) ? opt->faddr : ireq->rmt_addr,
            ireq->loc_addr, th->source, th->dest);
        security_req_classify_flow(req, flowi4_to_flowi(&fl4));
        rt = ip_route_output_key(sock_net(sk), &fl4);
        if (IS_ERR(rt)) {
            reqsk_free(req);
            goto out;
        }
    
        /* Try to redo what tcp_v4_send_synack did. */
        req->window_clamp = tp->window_clamp ? : dst_metric(&rt->dst, RTAX_WINDOW);
    
        /* 获取接收窗口的初始值,窗口扩大因子和接收窗口的上限 */
        tcp_select_initial_window(tcp_full_space(sk), req->mss, &req->rcv_wnd, &req->window_clamp,
            ireq->wscale_ok, &rcv_wscale, dst_metric(&rt->dst, RTAX_INITRWND));
        ireq->rcv_wscale = rcv_wscale;
    
        /* 到了这里,三次握手基本完成。
         * 接下来为新的连接创建和初始化一个传输控制块,并把它和连接请求块关联起来。
         * 最后把该连接请求块移入全连接队列中,等待accept()。
         */
        ret = get_cookie_sock(sk, skb, req, &rt->dst);    
    
        /* ip_queue_xmit() depends on our flow being setup
         * Normal sockets get it right from inet_csk_route_child_sock()
         */
        if (ret)
            inet_sk(ret)->cork.fl.u.ip4 = fl4;
    
    out: 
        return ret;
    }
    
    /* RFC 1122 initial RTO value, now used as a fallback RTO for the initial data
     * transmssion if no valid RTT sample has been accquired, most likely due to
     * retrans in 3WHS.
     */
    #define TCP_TIMEOUT_FALLBACK ((unsigned) (3 * HZ)) 
    
    /* syncookies: no recent synqueue overflow on this listening socket? 
     * 如果最近3s内没有发生半连接队列溢出,则为真。
     */
    static inline bool tcp_synq_no_recent_overflow(const struct sock *sk)
    {
        unsigned long last_overflow = tcp_sk(sk)->rx_opt.ts_recent_stamp;
        return time_after(jiffies, last_overflow + TCP_TIMEOUT_FALLBACK);
    }
    

     

    如果SYNACK段使用SYN Cookie,并且使用时间戳选项,则把TCP选项信息保存在SYNACK段中tsval的低6位。

    所以,现在收到ACK后,可以从ACK段的tsecr中提取出这些选项。

    /* When syncookies are in effect and tcp timestamps are enabled we stored addtional tcp
     * options in the timestamp.
     * This extracts these options from the timestamp echo.
     * The lowest 4 bits store snd_wscale.
     * next 2 bits indicate SACK and ECN support.
     * return false if we decode an option that should not be.
     */
    bool cookie_check_timestamp(struct tcp_options_received *tcp_opt, bool *ecn_ok)
    {
        /* echoed timestamp, lowest bits contain options */
        u32 options = tcp_opt->rcv_tsecr & TSMASK;
    
        /* 如果ACK没有携带时间戳,则把tcp_opt中的tstamp_ok、sack_ok、wscale_ok
         * snd_wscale和cookie_plus置零。
         */
        if (! tcp_opt->saw_tstamp) {
            tcp_clear_options(tcp_opt);
            return true;
        }
    
        if (! sysctl_tcp_timestamps)
            return false;
    
        tcp_opt->sack_ok = (options & (1 << 4)) ? TCP_SACK_SEEN : 0;
        *ecn_ok = (options >> 5) & 1;
    
        if (*ecn_ok && ! sysctl_tcp_ecn)
            return false;
    
        if (tcp_opt->sack_ok && ! sysctl_tcp_sack)
            return false;
    
        if ((options & 0xf) == 0xf)
            return true; /* no window scaling. */
    
        tcp_opt->wscale_ok = 1;
        tcp_opt->snd_wscale = options & 0xf;
        return sysctl_tcp_window_scaling != 0;
    }
    

     

    为新的连接创建和初始化一个传输控制块,然后把完成三次握手的req和新sock关联起来,

    并把该连接请求块移入全连接队列中。

    static inline struct sock *get_cookie_sock(struct sock *sk, struct sk_buff *skb,
         struct request_sock *req, struct dst_entry *dst)
    {
        struct inet_connection_sock *icsk = inet_csk(sk);
        struct sock *child;
    
        /* 为新的连接创建和初始化一个传输控制块。
         * 对于TCP/IPv4,实例为ipv4_specific,调用tcp_v4_syn_recv_sock()
         */
        child = icsk->icsk_af_ops->syn_recv_sock(sk, skb, req, dst);
    
        if (child)
            /* 把完成三次握手的连接请求块,和新的sock关联起来,并把它移入全连接队列中。*/
            inet_csk_reqsk_queue_add(sk, req, child); 
        else
            reqsk_free(req);
    
        return child;
    }
     
    static inline void inet_csk_reqsk_queue_add(struct sock *sk, struct request_sock *req, struct sock *child)
    {
        reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);
    }

     

    把完成三次握手的连接请求块,和新的sock关联起来,并把它移入全连接队列中,等待被accept()。

    static inline void reqsk_queue_add(struct request_sock_queue *queue, struct request_sock *req,
          struct sock *parent, struct sock *child)
    {
        req->sk = child; /* 连接请求块request_sock,关联了一个新sock */
        sk_acceptq_added(parent); /* 监听sock的全连接队列中的连接请求个数加一 */
    
        /* 全连接队列是一个FIFO队列,把req加入到队列尾部 */
        if (queue->rskq_accept_head == NULL)
            queue->rskq_accept_head = req;
        else
            queue->rskq_accept_tail->dl_next = req;
    
        queue->rskq_accept_tail = req;
        req->dl_next = NULL;
    }
    
    static inline void sk_acceptq_added(struct sock *sk)
    {
        sk->sk_ack_backlog++;
    }
    

     

    评价

     

    SYN Cookie技术由于在建立连接的过程中不需要在服务器端保存任何信息,实现了无状态的三次握手,从而有效的

    防御了SYN Flood攻击。但是该方法也存在一些弱点。由于cookie的计算只涉及到包头部分信息,在建立连接的过程

    中不在服务器端保存任何信息,所以失去了协议的许多功能,比如超时重传。此外,由于计算cookie有一定的运算量,

    增加了连接建立的延迟时间,因此,SYN Cookie技术不能作为高性能服务器的防御手段。通常采用动态资源分配机制,

    当分配了一定的资源后再采用cookie技术,Linux就是这样实现的。还有一个问题是,当我们避免了SYN Flood攻击的

    同时,也提供了另一种拒绝服务攻击方式,攻击者发送大量的ACK报文,服务器忙于计算验证。尽管如此,在预防

    SYN Flood供给方面,SYN Cookie技术仍然是有效的(引用自[1])。

     

    扩展

     

    Linux内核中的SYN Cookie机制主要的功能是防止本机遭受SYN Flood攻击。

    SYN Cookie Firewall利用SYN Cookie的原理,在内网和外网之间实现TCP三次握手过程的代理(proxy)。

    一些SYN攻击的防火墙也是基于SYN Cookie,只是把这个功能移动到内核之外的代理服务器上。

     

    Reference

     

    [1]. https://www.ibm.com/developerworks/cn/linux/l-syncookie/

     

    展开全文
  • 3.6 SYN Cookie

    千次阅读 2015-03-19 21:45:07
    在三次握手过程中,server端的TCP收到SYN请求后会建立一个request_sock保存在syn_table中。如果有恶意攻击者大量发送IP地址或端口不同的SYN包,则server端TCP的...SYN Cookie技术就是为了应对SYN Flood攻击而产生的,它
    在三次握手过程中,server端的TCP收到SYN请求后会建立一个request_sock保存在syn_table中。如果有恶意攻击者大量发送IP地址或端口不同的SYN包,则server端TCP的syn_table很快会被占满,而普通用户对server的正常访问会因为syn_table已满而被拒绝。这就是SYN Flood攻击。SYN Cookie技术就是为了应对SYN Flood攻击而产生的,它的原理是,在TCP服务器收到SYN包并返回SYN|ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值,在发送SYN|ACK时将这个cookie作为其序列号。在收到ACK包时,TCP服务器根据确认号得到之前的cookie,再根据这个cookie值检查这个ACK包的合法性。如果合法,再分配sock保存未来的TCP连接的信息。
        如果要Linux内核支持SYN Cookie功能,必须开启CONFIG_SYN_COOKIES编译选项。下面分析一下TCP对SYN Cookie功能的实现。

        TCP服务器在接收到SYN时,会调用tcp_v4_conn_request函数:

     1465 int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
    1466 {
    1467     struct tcp_options_received tmp_opt;
    1468     struct request_sock *req;
    1469     struct inet_request_sock *ireq;
    1470     struct tcp_sock *tp = tcp_sk(sk);
    1471     struct dst_entry *dst = NULL;  
    1472     __be32 saddr = ip_hdr(skb)->saddr;
    1473     __be32 daddr = ip_hdr(skb)->daddr;
    1474     __u32 isn = TCP_SKB_CB(skb)->when;
    1475     bool want_cookie = false;
    1476     struct flowi4 fl4;   
    1477     struct tcp_fastopen_cookie foc = { .len = -1 };
    1478     struct tcp_fastopen_cookie valid_foc = { .len = -1 };
    1479     struct sk_buff *skb_synack;    
    1480     int do_fastopen;     
    ...
    1486     /* TW buckets are converted to open requests without
    1487      * limitations, they conserve resources and peer is
    1488      * evidently real one.
    1489      */
    1490     if (inet_csk_reqsk_queue_is_full(sk) && !isn) {//如果存储request sock的accept队列已满且SYN包没有命中TIME_WAIT socekt
    1491         want_cookie = tcp_syn_flood_action(sk, skb, "TCP");//如果用户使用sysctl_tcp_syncookies开启了syn cookie功能,则want_cookie为1
    1492         if (!want_cookie)//如果使用syn cookie机制,不需要保存request sock,所以不需要受到accept queue的限制
    1493             goto drop;   
    1494     }
    ...
    1506     req = inet_reqsk_alloc(&tcp_request_sock_ops);//申请一个request sock,但不会保存到accept queue中
    1507     if (!req)
    1508         goto drop;
    ...
    1517     tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc);//如果使用syn cookie,则不能使用fast open功能,因为无处存储相关信息
    1518                 
    1519     if (want_cookie && !tmp_opt.saw_tstamp)//使用syn cookie时如果对端没有开启时间戳选项
    1520         tcp_clear_options(&tmp_opt);  //则本端不支持SACK和窗口扩大选项,因为无处存储相关信息
    ...
    1523     tcp_openreq_init(req, &tmp_opt, skb);
    ...
    1537     if (want_cookie) {//使用syn cookie的话,
    1538         isn = cookie_v4_init_sequence(sk, skb, &req->mss);//生成syn cookie并以之作为SYN|ACK的起始序列号
    1539         req->cookie_ts = tmp_opt.tstamp_ok;
    1540     } else if (!isn) {
    ...
    1578     tcp_rsk(req)->snt_isn = isn;   
    ...
    1598     skb_synack = tcp_make_synack(sk, dst, req,
    1599         fastopen_cookie_present(&valid_foc) ? &valid_foc : NULL); 
    ...
    1607     if (likely(!do_fastopen)) {
    1608         int err;
    1609         err = ip_build_and_send_pkt(skb_synack, sk, ireq->loc_addr,
    1610              ireq->rmt_addr, ireq->opt);
    1611         err = net_xmit_eval(err);
    1612         if (err || want_cookie)//发送完SYN|ACK后,丢弃此request sock
    1613             goto drop_and_free;
    ...
    1629 drop_and_free:
    1630     reqsk_free(req);
      1490:只有accpet队列满时才会启用syn cookie机制

      1538:生成cookie作为初始序列号isn

      1578:记录初始序列号,并在1598行的tcp_make_synack中被用作起始序列号:

    2654 struct sk_buff *tcp_make_synack(struct sock *sk, struct dst_entry *dst,
    2655                 struct request_sock *req,
    2656                 struct tcp_fastopen_cookie *foc)
    2657 {
    2658     struct tcp_out_options opts;
    2659     struct inet_request_sock *ireq = inet_rsk(req);
    2660     struct tcp_sock *tp = tcp_sk(sk);
    2661     struct tcphdr *th;
    ...
    2703     memset(&opts, 0, sizeof(opts));
    2704 #ifdef CONFIG_SYN_COOKIES
    2705     if (unlikely(req->cookie_ts))
    2706         TCP_SKB_CB(skb)->when = cookie_init_timestamp(req);  //生成时间戳
    2707     else
    2708 #endif
    2709     TCP_SKB_CB(skb)->when = tcp_time_stamp;
    2710     tcp_header_size = tcp_synack_options(sk, req, mss, skb, &opts, &md5,
    2711                          foc) + sizeof(*th);
    ...
    2726     tcp_init_nondata_skb(skb, tcp_rsk(req)->snt_isn,
    2727                  TCPHDR_SYN | TCPHDR_ACK);
    2728 
    2729     th->seq = htonl(TCP_SKB_CB(skb)->seq);
    2730     /* XXX data is queued and acked as is. No buffer/window check */
    2731     th->ack_seq = htonl(tcp_rsk(req)->rcv_nxt);
    2732 
    ...
      2706:cookie_init_timestamp函数会将窗口扩大选项和选择确认选项的信息编码成为时间戳的值:

     67 __u32 cookie_init_timestamp(struct request_sock *req)
     68 {
     69     struct inet_request_sock *ireq; 
     70     u32 ts, ts_now = tcp_time_stamp;
     71     u32 options = 0;
     72 
     73     ireq = inet_rsk(req);
     74 
     75     options = ireq->wscale_ok ? ireq->snd_wscale : 0xf; //窗口扩大选项信息
     76     options |= ireq->sack_ok << 4;  //选择确认选项信息
     77     options |= ireq->ecn_ok << 5;
     78 
     79     ts = ts_now & ~TSMASK;
     80     ts |= options;
     81     if (ts > ts_now) {
     82         ts >>= TSBITS;
     83         ts--;
     84         ts <<= TSBITS;
     85         ts |= options; 
     86     }
     87     return ts;
     88 }    
      赋给TCP_SKB_CB(skb)->when后再写入时间戳中:

    560 static unsigned int tcp_synack_options(struct sock *sk,
     561                    struct request_sock *req,
     562                    unsigned int mss, struct sk_buff *skb,
     563                    struct tcp_out_options *opts,
     564                    struct tcp_md5sig_key **md5,
     565                    struct tcp_fastopen_cookie *foc)
     566 {
     567     struct inet_request_sock *ireq = inet_rsk(req);
    ...
     596     if (likely(ireq->tstamp_ok)) {
     597         opts->options |= OPTION_TS;
     598         opts->tsval = TCP_SKB_CB(skb)->when;
     599         opts->tsecr = req->ts_recent;
     600         remaining -= TCPOLEN_TSTAMP_ALIGNED;
     601     }
    ...
      tcp_init_nondata_skb函数记录将MSS选项编码的cookie值:

     356 static void tcp_init_nondata_skb(struct sk_buff *skb, u32 seq, u8 flags)
     357 {
     358     skb->ip_summed = CHECKSUM_PARTIAL;
     359     skb->csum = 0;
     360 
     361     TCP_SKB_CB(skb)->tcp_flags = flags;
     362     TCP_SKB_CB(skb)->sacked = 0;
     363 
     364     skb_shinfo(skb)->gso_segs = 1;
     365     skb_shinfo(skb)->gso_size = 0;
     366     skb_shinfo(skb)->gso_type = 0;
     367 
     368     TCP_SKB_CB(skb)->seq = seq;  //记录序列号
     369     if (flags & (TCPHDR_SYN | TCPHDR_FIN))  //SYN或FIN占用一个序列号
     370         seq++;
     371     TCP_SKB_CB(skb)->end_seq = seq;
     372 }
      回到tcp_v4_conn_request函数,来看看生成cookie的cookie_v4_init_sequence函数:

    163 __u32 cookie_v4_init_sequence(struct sock *sk, struct sk_buff *skb, __u16 *mssp)
    164 {
    165     const struct iphdr *iph = ip_hdr(skb);
    166     const struct tcphdr *th = tcp_hdr(skb);
    167     int mssind;
    168     const __u16 mss = *mssp;
    169
    170     tcp_synq_overflow(sk);//记录SYN accept queue最后一次溢出的时间戳
    171
    172     for (mssind = ARRAY_SIZE(msstab) - 1; mssind ; mssind--)
    173         if (mss >= msstab[mssind])
    174             break;//找到一个最小的“最大MSS”值
    175     *mssp = msstab[mssind];
    176
    177     NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESSENT);
    178
    179     return secure_tcp_syn_cookie(iph->saddr, iph->daddr,
    180                      th->source, th->dest, ntohl(th->seq),
    181                      jiffies / (HZ * 60), mssind);//生成cookie
    182 }        
      secure_tcp_syn_cookie函数:

    91 static __u32 secure_tcp_syn_cookie(__be32 saddr, __be32 daddr, __be16 sport,
     92                    __be16 dport, __u32 sseq, __u32 count,
     93                    __u32 data)
     94 {
     95     /*
     96      * Compute the secure sequence number.
     97      * The output should be:
     98      *   HASH(sec1,saddr,sport,daddr,dport,sec1) + sseq + (count * 2^24)
     99      *      + (HASH(sec2,saddr,sport,daddr,dport,count,sec2) % 2^24).
    100      * Where sseq is their sequence number and count increases every
    101      * minute by 1.
    102      * As an extra hack, we add a small "data" value that encodes the
    103      * MSS into the second hash value.
    104      */
    105
    106     return (cookie_hash(saddr, daddr, sport, dport, 0, 0) +
    107         sseq + (count << COOKIEBITS) +
    108         ((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)
    109          & COOKIEMASK));//将data中存储的最大MSS的信息编码进cookie中
    110 }
      可见cookie_v4_init_sequence函数会把对端的最大报文段选项MSS的值编码进cookie的值中.

      客户端收到SYN|ACK后发送ACK给服务器,服务器收到后会调用tcp_v4_hnd_req函数进行处理:

    1739 static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
    1740 {
    1741     struct tcphdr *th = tcp_hdr(skb);
    1742     const struct iphdr *iph = ip_hdr(skb);
    1743     struct sock *nsk;
    1744     struct request_sock **prev;    
    1745     /* Find possible connection requests. */
    1746     struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
    1747                                iph->saddr, iph->daddr);
    1748     if (req)//由于没有建立request sock,故req为NULL
    1749         return tcp_check_req(sk, skb, req, prev, false);      
    ...
    1763 #ifdef CONFIG_SYN_COOKIES
    1764     if (!th->syn)
    1765         sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));//如果cookie合法,根据cookie生成socket
    1766 #endif
    1767     return sk;
    1768 }
      cookie_v4_check函数
    266 struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb,
    267                  struct ip_options *opt)
    268 {        
    269     struct tcp_options_received tcp_opt;
    270     struct inet_request_sock *ireq;
    271     struct tcp_request_sock *treq;
    ...
    286     if (tcp_synq_no_recent_overflow(sk) ||//listening socket的SYN accept queue最近没有溢出
    287         (mss = cookie_check(skb, cookie)) == 0) {//cookie不合法,没得到了收到SYN时存储的对端的MSS值
    288         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_SYNCOOKIESFAILED);
    289         goto out;
    290     }
    ...
    295     memset(&tcp_opt, 0, sizeof(tcp_opt));
    296     tcp_parse_options(skb, &tcp_opt, 0, NULL);
    297
    298     if (!cookie_check_timestamp(&tcp_opt, sock_net(sk), &ecn_ok))
    299         goto out;
    300
    301     ret = NULL;
    302     req = inet_reqsk_alloc(&tcp_request_sock_ops); /* for safety */
    303     if (!req)
    304         goto out;
    305
    306     ireq = inet_rsk(req);
    307     treq = tcp_rsk(req);
    308     treq->rcv_isn       = ntohl(th->seq) - 1;
    309     treq->snt_isn       = cookie;
    310     req->mss        = mss;  //记录解密后得到的MSS
    311     ireq->loc_port      = th->dest;
    312     ireq->rmt_port      = th->source;
    313     ireq->loc_addr      = ip_hdr(skb)->daddr;
    314     ireq->rmt_addr      = ip_hdr(skb)->saddr;
    315     ireq->ecn_ok        = ecn_ok;
    316     ireq->snd_wscale    = tcp_opt.snd_wscale;
    317     ireq->sack_ok       = tcp_opt.sack_ok;
    318     ireq->wscale_ok     = tcp_opt.wscale_ok;
    319     ireq->tstamp_ok     = tcp_opt.saw_tstamp;
    320     req->ts_recent      = tcp_opt.saw_tstamp ? tcp_opt.rcv_tsval : 0;
    321     treq->snt_synack    = tcp_opt.saw_tstamp ? tcp_opt.rcv_tsecr : 0;
    322     treq->listener      = NULL;
    323
    324     /* We throwed the options of the initial SYN away, so we hope
    325      * the ACK carries the same options again (see RFC1122 4.2.3.8)
    326      */
    327     if (opt && opt->optlen) {
    328         int opt_size = sizeof(struct ip_options_rcu) + opt->optlen;
    329
    330         ireq->opt = kmalloc(opt_size, GFP_ATOMIC);
    331         if (ireq->opt != NULL && ip_options_echo(&ireq->opt->opt, skb)) {
    332             kfree(ireq->opt);
    333             ireq->opt = NULL;
    334         }
    335     }
    ...
    362
    363     /* Try to redo what tcp_v4_send_synack did. */
    364     req->window_clamp = tp->window_clamp ? :dst_metric(&rt->dst, RTAX_WINDOW);
    365
    366     tcp_select_initial_window(tcp_full_space(sk), req->mss,
    367                   &req->rcv_wnd, &req->window_clamp,
    368                   ireq->wscale_ok, &rcv_wscale,
    369                   dst_metric(&rt->dst, RTAX_INITRWND));
    370
    371     ireq->rcv_wscale  = rcv_wscale;
    372
    373     ret = get_cookie_sock(sk, skb, req, &rt->dst);//生成socket
    374     /* ip_queue_xmit() depends on our flow being setup
    375      * Normal sockets get it right from inet_csk_route_child_sock()
    376      */
    377     if (ret)
    378         inet_sk(ret)->cork.fl.u.ip4 = fl4;
    379 out:    return ret;
    380 }
      cookie解码函数cookie_check会得到对端的MSS:

    195 static inline int cookie_check(struct sk_buff *skb, __u32 cookie)
    196 {
    197     const struct iphdr *iph = ip_hdr(skb);
    198     const struct tcphdr *th = tcp_hdr(skb);
    199     __u32 seq = ntohl(th->seq) - 1;
    200     __u32 mssind = check_tcp_syn_cookie(cookie, iph->saddr, iph->daddr,
    201                         th->source, th->dest, seq,
    202                         jiffies / (HZ * 60),           
    203                         COUNTER_TRIES);    //解码cookie,找到生成cookie时隐藏的msstab数组下标
    204
    205     return mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0;//如果cookie非法,则返回0,否则返回最大MSS的值
    206 }
      check_tcp_syn_cookie
    121 static __u32 check_tcp_syn_cookie(__u32 cookie, __be32 saddr, __be32 daddr,
    122                   __be16 sport, __be16 dport, __u32 sseq,
    123                   __u32 count, __u32 maxdiff)
    124 {   
    125     __u32 diff;
    126     
    127     /* Strip away the layers from the cookie */
    128     cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;
    129                         
    130     /* Cookie is now reduced to (count * 2^24) ^ (hash % 2^24) */
    131     diff = (count - (cookie >> COOKIEBITS)) & ((__u32) - 1 >> COOKIEBITS);//计算从发出cookie到收到此cookie经历了几分钟
    132     if (diff >= maxdiff)//如果超过了maxdiff分钟,即为非法cookie
    133         return (__u32)-1;
    134
    135     return (cookie -   
    136         cookie_hash(saddr, daddr, sport, dport, count - diff, 1))
    137         & COOKIEMASK;   /* Leaving the data behind */    //找到生成cookie时隐藏的信息
    138 }   
      cookie_check_timestamp函数会解码得到SACK和窗口扩大选项的信息:
    235 bool cookie_check_timestamp(struct tcp_options_received *tcp_opt,
    236             struct net *net, bool *ecn_ok)
    237 {   
    238     /* echoed timestamp, lowest bits contain options */
    239     u32 options = tcp_opt->rcv_tsecr & TSMASK;
    240     
    241     if (!tcp_opt->saw_tstamp)  {
    242         tcp_clear_options(tcp_opt);
    243         return true;    
    244     }
    245     
    246     if (!sysctl_tcp_timestamps)
    247         return false;   
    248 
    249     tcp_opt->sack_ok = (options & (1 << 4)) ? TCP_SACK_SEEN : 0;
    250     *ecn_ok = (options >> 5) & 1;
    251     if (*ecn_ok && !net->ipv4.sysctl_tcp_ecn)
    252         return false;
    253 
    254     if (tcp_opt->sack_ok && !sysctl_tcp_sack)
    255         return false;
    256 
    257     if ((options & 0xf) == 0xf)
    258         return true; /* no window scaling */
    259 
    260     tcp_opt->wscale_ok = 1;
    261     tcp_opt->snd_wscale = options & 0xf;
    262     return sysctl_tcp_window_scaling != 0;
    263 }
      后续流程与不使用SYN Cookie的情况是一样的.

      由此看出SYN Cookie的原理:

    1、Server端收到SYN请求后不建立request_sock保存连接信息,而是将SYN包中的MSS值编码进cookie中,并将cookie作为初始序列号写入SYN|ACK中;如果SYN中开启了时间戳,则将SYN的SACK选项和窗口扩大选项信息编码进时间戳的值中,再将这个时间戳写入SYN|ACK中,最后将SYN|ACK发送出去

    2、Client端收到SYN|ACK后,按照TCP协议的标准处理来发送ACK包;Server端在收到ACK后,从ACK的ack_seq中得到SYN请求的MSS值,从ACK包的时间戳回显值得到SYN请求中的SACK和窗口扩大选项;根据这些选项的信息建立新的sock

      根据上述原理,在TCP开启SYN Cookie功能的情况下如果攻击者想让SYN Flood攻击奏效,则必须完成全部三次握手流程才行,这样会加重攻击者的负担,减小攻击的危害。开启SYN Cookie功能的缺点是:如果client端没有开启时间戳,则SACK和窗口扩大选项无法使用。

    展开全文
  • SYN Cookie原理

    2013-11-20 00:19:41
    到目前为止,能够有效防范SYN Flood攻击的手段并不多,而SYN Cookie就是其中最著名的一种。SYN Cookie原理由D. J. Bernstain和 Eric Schenk发明。在很多操作系统上都有各种各样的实现。其中包括Linux。本文就分别...

    概述

    在目前以IPv4为支撑的网络协议上搭建的网络环境中,SYN Flood是一种非常危险而常见的DoS攻击方式。到目前为止,能够有效防范SYN Flood攻击的手段并不多,而SYN Cookie就是其中最著名的一种。SYN Cookie原理由D. J. Bernstain和 Eric Schenk发明。在很多操作系统上都有各种各样的实现。其中包括Linux。本文就分别介绍一下SYN Flood攻击和SYN Cookie的原理,更重要的是介绍Linux内核中实现SYN Cookie的方式。最后,本文给出一种增强目前Linux中SYN Cookie功能的想法。

    一 SYN Flood攻击

    SYN Flood攻击是一种典型的拒绝服务型(Denial of Service)攻击。所谓拒绝服务型攻击就是通过进行攻击,使受害主机或网络不能够良好的提供服务,从而间接达到攻击的目的。

    SYN Flood攻击利用的是IPv4中TCP协议的三次握手(Three-Way Handshake)过程进行的攻击。大家知道协议规定,如果一端想向另一端发起TCP连接,它需要首先发送TCP SYN 包到对方,对方收到后发送一个TCP SYN+ACK包回来,发起方再发送TCP ACK包回去,这样三次握手就结束了。我们把TCP连接的发起方叫作"TCP客户机(TCP Client)",TCP连接的接收方叫作"TCP服务器(TCP Server)"。值得注意的是在TCP服务器收到TCP SYN request包时,在发送TCP SYN+ACK包回TCP客户机前,TCP服务器要先分配好一个数据区专门服务于这个即将形成的TCP连接。一般把收到SYN包而还未收到ACK包时的连接状态成为半开连接(Half-open Connection)。

    在最常见的SYN Flood攻击中,攻击者在短时间内发送大量的TCP SYN包给受害者,这时攻击者是TCP客户机,受害者是TCP服务器。根据上面的描述,受害者会为每个TCP SYN包分配一个特定的数据区,只要这些SYN包具有不同的源地址(这一点对于攻击者来说是很容易伪造的)。这将给TCP服务器系统造成很大的系统负担,最终导致系统不能正常工作。

    二 SYN Cookie原理

    SYN Cookie是对TCP服务器端的三次握手协议作一些修改,专门用来防范SYN Flood攻击的一种手段。它的原理是,在TCP服务器收到TCP SYN包并返回TCP SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。在收到TCP ACK包时,TCP服务器在根据那个cookie值检查这个TCP ACK包的合法性。如果合法,再分配专门的数据区进行处理未来的TCP连接。

    从上面的介绍可以看出,SYN Cookie的原理比较简单。到实际的应用中,它有多种不同的实现方式。

    三 Linux内核中的SYN Cookie实现

    Linux内核中对SYN Flood有很好的防护。以下的讨论都是针对Linux2.4.20内核进行的。在每一个sock都有一个tcp_opt即这个sock的TCP选项。在tcp_opt其中有一个tcp_listen_opt,这里存储的是这个sock在LISTEN状态下时保存的一些选项,其中有一个open_request结构的数组,数组长度为TCP_SYNQ_HSIZE(512)。所有这些表示在一个sock,最多可以同时开启512个半开连接(这是在不考虑其他约束条件时的最大值,实际情况中不会达到这个值)。当这个数组满了时,新来的open_request会顶替掉一个老的open_request。这样,即使没有启动SYN Cookie,也能够在SYN Flood发生时保护系统免于瘫痪。问题是这种处理方法会在面对SYN Flood攻击时丢掉正常的TCP连接请求。SYN Cookie的作用恰恰是保证在面对SYN Flood攻击时,一方面能够拒绝非法的TCP连接请求,一方面正常连接可以被建立。

    Linux内核对TCP流程的处理主要在tcp_ipv4.c文件中的函数实现。具体的,当处理TCP SYN包时,系统进入tcp_v4_conn_request函数。其中调用cookie_v4_init_sequence生成一个ISN(Initial Sequence Number)。Linux内核把它作为SYN Cookie流程中的cookie。

    cookie_v4_init_sequence函数在syncookies.c文件中定义,它又调用random.c文件中的secure_tcp_syn_cookie函数。cookie的实质计算是在这个函数中进行的。

    在random.c文件里给出secure_tcp_syn_cookie函数的定义之前给出两个宏,它们的定义分别为

        #define COOKIEBITS 24
        #define COOKIEMASK (((__u32)1 << COOKIEBITS) - 1)
        

    COOKIEBITS表示cookie的比特长度;COOKIEMASK是一个COOKIEBITS长的比特 串,所有比特都是1。

    还有两个比特串,被定义成一个__u32的二维数组

        static __u32	syncookie_secret[2][16-3+HASH_BUFFER_SIZE];
        

    其中所有的比特值在secure_tcp_syn_cookie中被随机的赋予,用get_random_bytes函数。它们成为制作cookie的密钥。这两个被随机产生的比特串是整个SYN Cookie实现方案的关键。另外还有一个开关syncookie_init控制对这两个密钥的改动。

    还需要指出,在文件syncookies.c中定义有一个__u16组成的表static __u16 const msstab[],这个表中保存的是一些可能的MSS(Maximum Segment Size)值。

    secure_tcp_syn_cookie函数的返回值就是计算得到的ISN值,即cookie。为了描述方便,我们给出如下定义:

    	tmp1 := saddr + daddr + ((sport<<16)+dport) + syncookie_secret[0]
    	tmp2 := saddr + daddr + ((sport<<16)+dport) + syncookie_secret[1]
    	tmp11 := HASH_TRANSFORM(tmp1[16], tmp1)
    	tmp22 := HASH_TRANSFORM(tmp2[16], tmp2)
        A := tmp11[0][17]
    	B := tmp22[1][17]
    	

    sseq := ntohl(skb->h.th->seq) 这里的skb是携带TCP SYN的那个skb 
    count1 := jiffies/(HZ*60) 当前时间的分钟值 
    data1 := msstab 
    从前往后最后一个小于skb中携带的MSS值的值的索引(值得注意的是两个密钥在第一次被初始化后,就不会再有改动,直到系统重新启动。因此可以认为它是一个常值。)

    有了上面的定义我们可以得到cookie等于

            isn := A+sseq + (count1<<COOKIEBITS) + (B+data1)&COOKIEMASK
            

    这个isn被赋予返回的TCP SYN+ACK包中,作为其中的ISN值。这就是cookie 的产生过程。在这个过程中,没有在本地为这个连接请求分配任何存储空间。

    在TCP服务器收到TCP ACK包时,相应的要进行SYN Cookie的检查。这个检查 过程在函数tcp_v4_hnd_req中的cookie_v4_check函数开始。cookie_v4_check调用cookie_check函数,cookie_check函数调用check_tcp_syn_cookie函数。

    check_tcp_syn_cookie函数在random.c中定义,是与前面介绍的 
    secure_tcp_syn_cookie函数对应的函数,检查从TCP ACK中提取出的ISN值。

    在check_tcp_syn_cookie中假定ISN的值如下

            isn := A+sseq + (count2<<COOKIEBITS) + (B+data2)&COOKIEMASK
            

    这里的A、B都是根据当前这个skb中的地址信息和syncookie_secret算出来的;sseq是根据这个skb中的seq值算出的。

    有了上面这些值,TCP服务器就可以反算出count2和data2。理论上来说,只要这个isn是原来那个isn,应该有

    count2 == count1  
    data2 == data1
    

    但是这种结论仅仅是一个理论情况。因为在TCP服务器端并没有保存原来的count1和data1,因此不能直接进行比较。TCP服务器采取的方法是:

    1)计算出当前的分钟值 
    count3 := jiffies/(HZ*60) 
    用count3与count2比较,如果差值超过COUNTER_TRIES(4)分钟,则认为这 个ACK包不合法。 
    2)看data2是不是一个合法的msstab的索引,也就是说是不是小于NUM_MSS, 即(sizeof(msstab)/sizeof(msstab[0]) - 1)。如果小于,则认为这个ACK 合法,否则认为非法。

    上面介绍的就是Linux内核Linux2.4.20中对SYN Cookie的实现方式。下面讨论一下它的合理性。希望得到的结论是这种方案可以有效的实现一般TCP的连接,同时可以防止SYN Flood攻击。

    从上面的介绍来说,合法的TCP连接请求一定可以通过SYN Cookie流程。 另一方面我们看SYN Cookie在系统受到各种SYN Flood攻击时会采取的行为。 最一般的SYN Flood攻击方式是攻击者作为TCP客户机发送大量TCP SYN包而不再发送其他的包。这时SYN Cookie会为每个SYN包计算出相应的ISN值,并返回SYN+ACK包,而在本地将不分配任何存储空间,因此不会被成功攻击。

    根据SYN Cookie的原理,攻击者有可能直接发送大量ACK包。这时SYN Cookie提取出每个包的isn值,并假定它有下面的格式

            isn := A+sseq + (count<<COOKIEBITS) + (B+data)&COOKIEMASK
            

    反算出count和data。

    因为攻击者并不知道这里的A和B,因此经过反算出的count和data几乎不可能都合理,因此TCP服务器也几乎不可能为这些ACK包分配存储空间,这也就说明了SYN Cookie达到起到了抵挡SYN Flood攻击的作用。

    四 SYN Cookie Firewall

    从上面的介绍可以看到,Linux内核中的SYN Cookie机制主要的功能是防止本机遭受SYN Flood攻击的,但是在很多情况下,仅仅实现这样的SYN Cookie机制是不够的。如果我们要考虑的是一个网关模式的防火墙,它不仅要保护本机免受各种网络攻击,还要保护它后面的所有对外有开放TCP端口的主机免受这些攻击。比如一个局域网中有个服务器开放了FTP服务给外界,这个服务器主机就有可能遭受到来自互联网上的SYN Flood攻击。而这时的防火墙会将所有的攻击SYN包转发给受害主机。

    一种杜绝这种情况的方法是SYN Cookie Firewall。它是SYN Cookie的一种扩展形式。总的来说,它是利用原来SYN Cookie的原理在内网和外网之间实现TCP三次握手过程的代理(proxy)的机制。

    为了方便描述,我们假定一个外在的TCP客户机C希望通过防火墙F连接到局域网中的一个TCP服务器S。

    在防火墙收到来自外网的SYN包时,它并不直接进行转发,而是缓存在本地,再按照原来SYN Cookie的机制制作好一个针对这个SYN包的SYN+ACK包,注意,这个SYN+ACK包中的ack顺序号为特制的cookie值c,更重要的是这个包的的源地址被伪造成了S的地址(为了描述方便,我们这里暂时不考虑NAT等其他因素)。这样C会接收到这个SYN+ACK包,并认为是从S反馈回来的。于是C再响应一个ACK包,并认为与S的TCP连接已经建立起来。这时防火墙F收到这个ACK包,按照前面的描述的SYN Cookie原理来检查这个ACK中的ack顺序号。如果认为合法,F将本地缓存的来自C的SYN包发送给S,这时S会响应一个SYN+ACK包到C,其中也携带一个seq号, 我们设为c`。当然这个包不会到达C,而是由防火墙F截取,F根据这个包中的序列号等信息,造一个ACK包响应到S。这时的情况是:C认为自己已经与S建立了TCP连接;S认为自己与C建立了TCP连接。以后的TCP数据内容可以直接穿过防火墙F,在S和C之间交互。


     

    上图是SYN Cookie Firewall的工作原理,它相当于在TCP Server与TCP Client之间实现了对三次握手协议的代理。第一次"三次握手"在TCP Client与防火墙之间进行,第二次"三次握手"在防火墙与TCP Server之间。在第一次"三次握手"时使用前面介绍的SYN Cookie流程。有一个问题在进行两次"三次握手"时出现了:如图所示,进行第一次"三次握手"后,TCP Client认为后续数据包的seq值从c+1开始,而进行第二次"三次握手"后,TCP Server认为后续发来的数据包的seq值从c`+1开始, c是cookie,c`是TCP Server随机产生的。c和c`几乎不可能相等,也就是说在完成上面的两个"三次握手"后,如果不进行其他操作,后续从TCP Client到TCP Server的数据包都将被认为顺序号不对而被丢掉。一种补救方法就是在防火墙本地保存一个值δ 
    δ = |c - c`| 
    利用这个差值,在每个数据包经过防火墙时,将其seq值修改一下,这样,后续的数据流量可以完美地在TCP Server和TCP Client之间传输了。

    总结

    现在普遍使用的IPv4协议带有很多安全上的问题,其中面对SYN Flood攻击的软弱就是一点。在不改变TCP三次握手流程的情况下,TCP Server几乎不可能有效的防范SYN Flood的攻击。要保证完全防范SYN Flood,必须修改三次握手协议。SYN Cookie是一种很有效的方法。它的思想比较简单,主要是如何具体的实现,Linux系统也提供了一种实现。作者通过研读Linux2.4.20内核中的代码,基本了解了Linux内核中实现SYN Cookie的手段,将其总结成文字,与对SYN Cookie同样感兴趣的朋友分享、交流。

    展开全文
  • How to generate SYN cookie?

    2020-11-27 16:47:19
    SYN Cookie: a cookie that is crafted based on host, port and current time with 1 minute accuracy". How to combine host,port and time for scrambling by MD5? And also, md5 is a 128bit checksum, but ...
  • <p>a: set server syncookie threshhold to 16 to trigger server start syncookie mode earlier than normal b: start tcpdump on server to capture packet from epwget c: use epwget to generate tcp connection...
  • 测了一次tcp syncookie的抗D性能,发现了一件有趣的事情,周末写一篇随笔出来。 请看下面的时序: 简单讲就是在syncookie被触发的时候,客户端可能会被静默丢掉最多3个字节,所谓静默就是客户端认为这些字节被收到...
  • SYN Cookie在Linux内核中的实现  在目前以IPv4为支撑的网络协议上搭建的网络环境中,SYN Flood是一种非常危险而常见的DoS攻击方式。到目前为止,能够有效防范SYN Flood攻击的手段并不多,而SYN Cookie就是其中最...
  •  Linux内核中提供了SYN Cookie的检验机制,用来防御SYN Flood攻击。因此,延伸出来的在SYN Cookie Firewall,用来验证SYN连接,并对通过验证报文...http://www.ibm.com/developerworks/cn/linux/l-syncookie/index
  • 个人理解TCP中SYN Cookie

    2019-09-28 01:37:56
    说起SYN Cookie还是得从TCP3次握手开始说起,先给出计网的体系结构图 然后解释一下SYN,seq,ack,ACK的相关名词 SYN(建立连接)ACK(确认后全部为1)PSH(传送)FIN(结束)RST(重置)URG(紧急) 产生SYN ...
  • F5的syn cookie防护技术

    2009-12-14 22:48:38
    F5基于syn cookie技术防护DOS攻击
  • SYN Cookie的缺陷: 1、无法理解SYN包中一些字段,例如MSS、时间戳等 2、由于无状态机制,如果客户端回应的ACK在传输过程中丢掉了,就会导致客户端和服务器端状态的不一致,客户端认为连接已经建立,但是服务器端...
  • Linux SYN Cookie的原理以及代码实现

    千次阅读 2017-08-27 11:03:09
    SYN Flood   下面这段介绍引用自[1]. ...SYN Cookie就是其中最著名的一种。   SYN Flood攻击是一种典型的拒绝服务(Denial of Service)攻击。所谓的拒绝服务攻击就是通过进行攻击,使受害主机或 网络
  • SYNCookie原理

    2017-10-06 11:16:44
    发起一个TCP 连接时,客户端将一个SYN包发送给服务器。作为响应,服务器将SYN + ACK 包返回给客户端。此数据包中有一个序号,它被TC 用来重新组装数据流。   SYN Cookies是根据以下规则构造的初始序号: 令t为一...
  • SYN flood攻击及SYN cookie原理分析

    千次阅读 2020-02-21 20:40:57
    最近在学习《计算机网络》,课程布置了一个网络攻击的作业 SYN Flooding Attack ,本篇博客就本次实验做一个完整的实验报告及总结。 所用平台seed简介 SEED:计算机安全教育的教学实验平台 http://www.cis.syr.edu/...
  • SYN Flood和SYN cookie

    2015-04-22 16:49:48
    http://blog.csdn.net/zhangskd/article/details/16986931
  • 到目前为止,能够有效防范SYN Flood攻击的手段并不多,而SYN Cookie就是其中最著名的一种。SYN Cookie原理由D. J. Bernstain和 Eric Schenk发明。在很多操作系统上都有各种各样的实现。其中包括Linux。本文就分别...

空空如也

空空如也

1 2 3 4 5 ... 17
收藏数 326
精华内容 130
关键字:

syncookie