synproxy笔记整理

  ·········Linux的TCP实现中自带了Syncokkie,然而那是在TCP层做的,我们知道Linux内核的TCP是在锁住Listener的情况下进行Syncookie过程的,这样做的意义明显是为了不为SYN攻击流量分配任何本地内存,而不是为了节省CPU时间的。为了去掉那把锁在Listener头上的那把自旋锁,即不为攻击者浪费本地内存,又不为其浪费CPU时间,就必须要在TCP的下层来完成握手过程的Syncookie处理,有这东西吗?
  非常幸运,我们目前有可用的SYNPROXY

  目前给同事讲解synproxy的作用并分析其抗ddos的能力,以及移植synproxy相关熟悉到网络设备上去。

之前有一篇文章讲解synproxy,文章如下

目前主要分析synproxy_tg4_reg 以及ipv4_synproxy_ops 何时起作用?

 

static struct xt_target synproxy_tg4_reg  = {//__read_mostly
    .name        = "SYNPROXY",
    .family        = NFPROTO_IPV4,
    .hooks        = (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_FORWARD),
    .target        = synproxy_tg4,
    .targetsize    = sizeof(struct xt_synproxy_info),
    .checkentry    = synproxy_tg4_check,
    .destroy    = synproxy_tg4_destroy,
    .me        = THIS_MODULE,
};

static struct nf_hook_ops ipv4_synproxy_ops[]  = {
    {
        .hook        = ipv4_synproxy_hook,
        .pf        = NFPROTO_IPV4,
        .hooknum    = NF_INET_LOCAL_IN,
        .priority    = NF_IP_PRI_CONNTRACK_CONFIRM - 1,
    },
    {
        .hook        = ipv4_synproxy_hook,
        .pf        = NFPROTO_IPV4,
        .hooknum    = NF_INET_POST_ROUTING,
        .priority    = NF_IP_PRI_CONNTRACK_CONFIRM - 1,
    },
};

 

第一个问题synproxy_tg4_reg什么时候生效?

  在filter表处命中根据match命中target。由于NF_INET_LOCAL_IN + NF_INET_FORWARD 处。所以只能在local_in 以及forward处生效。由于设置的规则在forward链表处,

 

 

 所以在chain forward 处理命中-p tcp --dport 80 -m state UNTRACKED,INVALID 后就执行SYNPROXY的target。

命中SYNPROXY后回复syn+ack时,由于ct=NULL,在local_out以及post_routing处检测到ct=NULL,就会直接返回(包括ipv4_synproxy_hook)。

对于后续第三次握手ack则会命中--stat  invalid这个选项,所以会在forwar处执行synproxy_tg4_reg,其结果是向server发出syn,

在发出syn报文时,经过local_out,创建新的ct,后续在post_routing的时候命中ipv4_synproxy_hook以及ipv4_confirm().

 

 

 

 

 

采用SYNPROXY进行DDoS防御的所有配置全部列出来:

 # 不为不请自来(没有经过三次握手)的数据包关联nf_conntrack表项
 sysctl -w net.netfilter.nf_conntrack_tcp_loose=0
 # 特殊对待携带SYN标记的数据包,由于仅仅收到SYN无法判断是否能最终完成握手,因此不能判断是否最终能关联nf_conntrack表项,故NOTRACK,交由SYNPROXY处理
 iptables -t raw -A PREROUTING -i eth4 -p tcp -m tcp --syn --dport 80 -j CT --notrack
 # SYNPROXY会过滤出两类感兴趣数据包自己来处理:
 #  1.UNTRACKED:携带SYN标记的数据包
 #  2.INVALID:未携带SYN标记的数据包(稍后我们会看到,其实这类包就是握手的ACK包或者攻击包)
 iptables -A INPUT -i eth4 -p tcp --dport 80 -m state --state UNTRACKED,INVALID     -j SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460
 # 丢弃未被SYNPROXY处理的数据包,此数据包为攻击包
 iptables -A INPUT -i eth4 -p tcp --dport 80 -m state --state INVALID   -j DROP

 

 

 

 

Netfilter connections can be manipulated with the user-space tool conntrack.

iptables can make use of checking the connection's information such as states, statuses and more to make packet filtering rules more powerful and easier to manage. The most common states are:

“NEW”: trying to create a new connection
“ESTABLISHED”: part of an already-existing connection
“RELATED”: assigned to a packet that is initiating a new connection and which has been “expected”. The aforementioned mini-ALGs set up these expectations, for example, when the nf_conntrack_ftp module sees an FTP “PASV” command.
“INVALID”: the packet was found to be invalid, e.g. it would not adhere to the TCP state diagram.
“UNTRACKED” is a special state that can be assigned by the administrator to bypass connection tracking for a particular packet (see raw table, above)

 

 

 

ipv4_synproxy_hook 主要处理:向server发送syn,等待server回复syn+ack。此过程中涉及到时间戳、seq序列号等的调整。

总结:流程经过如下:

client-----synproxy 报文交互:

syn---->prerouting(notrack)------forward(synproxy_tg4命中)---->syn+ack--->local_out (ct=NULL 不跟踪)------>postrouting(ct=NULL不跟踪)

  • SYNPROXY将会捕获该报文,记录报文中的相关信息,然后模仿服务器A发送一个TCP SYN+ACK 给客户端(源IP是服务器的IP),该报文从OUTPUT节点出去,由于syn报文没有进行连接跟踪,并且设置了nf_conntrack_tcp_loose=0。synack报文在会被连接跟踪设置为INVALID(注意不是UNTRACKED),不会创建CT。

 

ack---->prerouting(属于invalid)-----forward(synproxy_tg4命中)---->发出syn到server---->local_out(创建ct)----->postrouteing(命中ipv4_synproxy_hook + ipv4-confirm)---->server

 

  • 客户端回应一个 TCP ACK,同理该报文也会被设置为INVALID,报文将会命中第二条规则,执行SYNPROXY动作。
  • (tn->tcp_loose == 0) {/* 严格情况,直接不让通过,这里就是nf_conntrack_tcp_loose == 0的作用 */ /* Don't try to pick up connections. */ return false;//直接返回false,不进行连接跟踪。见代码tcp_new

发出syn到server---->local_out(创建ct)----->postrouteing(命中ipv4_synproxy_hook + ipv4-confirm)---->server

  • SYNPROXY发送一个TCP SYN真实服务器server A. 这是一个新的连接,该报文通过OUTPUT节点进入netfilter,该报文会创建连接跟踪,状态为NEW。源IP是客户端的源IP,目的IP是实服务器的IP。
  • 真实服务器server A发送一个SYN+ACK给客户端。
  • SYNPROXY收到来自真实服务器的SYN+ACK报文后,将会回应一个ACK报文,CT的状态被标记为ESTABLISHED。
  • 一旦连接跟踪进入 ESTABLISHED状态,SYNPROXY将会让客户端与真实服务器直接通信

 

 

synproxy--->client的时候需要使用syncookie。将mss掩藏到初始序列号中。如果不能使用syncookie则直接调用通告ece。

 

 

    • 措施1:不为不请自来的数据包创建nf_conntrack表项

      nf_conntrack机制中有一个特性,该特性是nf_conntrack_tcp_loose系统参数带来的:

      nf_conntrack_tcp_loose - BOOLEAN 0 - disabled
      not 0 - enabled (default)
      If it is set to zero, we disable picking up already established connections.

      它的意思是,是否仅仅允许为经过TCP三次握手的流创建nf_conntrack表项还是说为任意收到的TCP数据包(有可能是一个构造出来的攻击包)查询未果后均创建新的nf_conntrack表项。
        我们只需要将nf_conntrack_tcp_loose设置为0即可:

      sysctl -w net.netfilter.nf_conntrack_tcp_loose=0

      这样一来,那些铺天盖地而来的攻击数据包显然是未经三次握手的(这种攻击也叫做ACK攻击),自然而然也就不会为它们创建任何nf_conntrack表项了。既然识别了不关联任何表项的数据包都是恶意的攻击包,那么显然下面的iptables规则将会阻止这类数据包进一步深入协议栈:

      iptables -A INPUT -i eth4 -p tcp -m state --state INVALID -j DROP

      这条规则会将数据包阻止在IP层。因此我们已经有了对抗纯ACK攻击(即发送海量仅携带ACK标识的数据包)的手段。
        通过这个措施,便可以避免nf_conntrack为ACK攻击创建,删除不必要的表项,节省大量的CPU时间。

    • 措施2:保证只有完成TCP三次握手的才创建nf_conntrack表项

      要识别TCP握手过程,我们要对携带SYN标记的数据包特殊对待。
        在上一条中,我们通过iptables规则丢弃了INVALID的不请自来的攻击数据包,那么还有一种攻击数据包,它们携带了SYN标记,看样子不是不速之客,而且要来握手建连接的,如果它们中有的完成了三次握手,那么OK,这不是攻击数据包,但如果只是发了了SYN包再无下文了,那么很显然,这就是SYN Flood攻击。问题是如何知道它们能不能完成三次握手呢?
        这需要有一种机制可以Hold on整个握手过程,那势必就是Syncookie技术了

 

三次握手之后依然查询不到表项的数据包直接丢弃

 

根据以上的第1点和第2点措施,我们现在可以保证,凡是没有成功关联到nf_conntrack的数据包,均为攻击数据包!果断丢弃这些数据包可以节省巨量的CPU处理时间

 

由于syn报文没有创建连接跟踪,导致后续的syn+ack,ack报文以new的状态进入连接跟踪,会调用如下函数:最后生成不跟踪contrack

 

 

nf_conntrack_in
----->resolve_normal_ct
--------------->init_conntrack
--------------------->l4proto->new(ct, skb, dataoff, timeouts)
//也就是在init_contrack里面调用tcp_new函数

/* Called when a new connection for this protocol found. */
static bool tcp_new(struct nf_conn *ct, const struct sk_buff *skb,
            unsigned int dataoff, unsigned int *timeouts)
{
    ...

    /* Don't need lock here: this conntrack not in circulation yet 获取下一个状态*/
    new_state = tcp_conntracks[0][get_conntrack_index(th)][TCP_CONNTRACK_NONE];
    //new_state会被设置为TCP_CONNTRACK_MAX
    /* Invalid: delete conntrack 不合适状态,直接放弃连接跟踪 */
    if (new_state >= TCP_CONNTRACK_MAX) {
        pr_debug("nf_ct_tcp: invalid new deleting.\n");
        return false;
    }

    if (new_state == TCP_CONNTRACK_SYN_SENT) {/* 首包,初始化相关信息 */
        memset(&ct->proto.tcp, 0, sizeof(ct->proto.tcp));
        /* SYN packet */
        ct->proto.tcp.seen[0].td_end =
            segment_seq_plus_len(ntohl(th->seq), skb->len,
                         dataoff, th);
        ct->proto.tcp.seen[0].td_maxwin = ntohs(th->window);
        if (ct->proto.tcp.seen[0].td_maxwin == 0)
            ct->proto.tcp.seen[0].td_maxwin = 1;
        ct->proto.tcp.seen[0].td_maxend =
            ct->proto.tcp.seen[0].td_end;

        tcp_options(skb, dataoff, th, &ct->proto.tcp.seen[0]);
    } else if (tn->tcp_loose == 0) {/* 严格情况,直接不让通过,这里就是nf_conntrack_tcp_loose == 0的作用 */
        /* Don't try to pick up connections. */
        return false;//直接返回false,不进行连接跟踪。
    } else 
    ...
    return true;
}

 

 建立连接后:时间调整

请求方向需要修改回显时间戳,应答方向需要修改发送时间戳。

 //发往客户端的tcp window update报文会走这里进行时间戳调整。----------------------在synproxy_hook里面
    synproxy_tstamp_adjust(skb, thoff, th, ct, ctinfo, synproxy);

 

seqadj调整

  由于synproxy在给server发送syn报文的时候,使用了和client发送给synproxy相同的序列号,所以seqadj只需要修改请求方向的应答序列号和应答方向的发送序列号。

client在于synproxy建立连接的时候,synproxy是无法确定sever的初始序列号,并且synproxy使用了syncookie生成初始序列号,导致了synproxy发送给client的初始序列号,

与server的初始序列号不一致,从而产生了seqadj的需求。

synproxy在收到sever的syn+ack报文的时候会调用函数nf_ct_seqadj_init(ct, ctinfo, synproxy->isn - ntohl(th->seq));初始化时间戳扩展控制块上下文,然后在ipv4_confirm函数中负责调整

int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
              s32 off)
{
    enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
    struct nf_conn_seqadj *seqadj;
    struct nf_ct_seqadj *this_way;

    if (off == 0)
        return 0;

    set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);

    seqadj = nfct_seqadj(ct);
    this_way = &seqadj->seq[dir];
    this_way->offset_before     = off;//两个值是一样的,
    this_way->offset_after     = off;
    return 0;
}

目前看调整的序列号差值和alg里面的不一样;

注意SYNPROXY仅需要调整服务端到PROXY的序号,以及PROXY到服务端的确认序号。不需要调整客户端到SYNPROXY的序号,也无需调整PROXY到客户端的确认序号。

 

posted @ 2023-03-14 19:52  codestacklinuxer  阅读(272)  评论(0编辑  收藏  举报