searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Linux内核SYNPROXY的使用及原理简介

2023-12-20 02:38:17
131
0

SYNPROXY是Linux内核基于连接跟踪实现的一个内核模块,可以用来缓解tcp syn flood攻击。

SYNPROXY扮演client端和server端的中间人(代理)角色,先和client端完成三次握手,确认三次握手成功后,才会和server端进行三次握手,这样就能有效的拦截掉无效的连接(如tcp syn flood攻击),避免浪费server端资源。

一、使用

可以通过下面的配置,对本机的tcp的80端口开启SYNPROXY。

# 禁止中间捡起连接,禁止client发送ack给synproxy的时候创建ct,这样ack才会命中第二条iptables规则,然后由synproxy模块处理
# The purpose of disabling loose tracking is to have the final ACK from the
# client not be picked up by conntrack, so it won't create a new conntrack
# entry and will be marked INVALID and also get directed to the target.
echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_loose

# 第一条iptables规则
# 不对需要做synproxy的流量进行连接跟踪,这个做法有两个作用:
# 1. 可以让需要做synproxy的报文命中下面那条iptables规则。
# 2. 连接跟踪由synproxy向server进行3次握手的时候创建,这样ipv4_synproxy_hook()函数才可以根
#    据连接跟踪的状态来判断要不要进行SYN报文的重传!
iptables -t raw -A PREROUTING -i eth0 -p tcp --dport 80 --syn -j NOTRACK

# 第二条iptables规则
# 将untracked、invalid的报文导到SYNPROXY模块来处理
# 如果server不是本机,那么规则要添加到FORWARD链,即"iptables -A FORWARD ...."
iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state UNTRACKED,INVALID \
    -j SYNPROXY --sack-perm --timestamp --mss 1480 --wscale 7 --ecn

# 第三条iptables规则
# 丢弃未被SYNPROXY处理的报文,这些报文认为是攻击包
iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state INVALID   -j DROP

二、原理

  • 当开启synproxy功能的时候,synproxy相对于客户端是透明的。三次握手首先是在客户端和synproxy之间进行:
    • client端发送TCP SYN 给server端。
    • 当报文到达防火墙的时候,通过上面的第一条iptables规则将其设置为UNTRACKED,那么该syn报文不会进行连接跟踪。
    • 这个UNTRACKED TCP SYN报文将会命中第二条规则,执行SYNPROXY动作。
    • SYNPROXY将会捕获该报文,记录报文中的相关信息,然后模仿server发送一个TCP SYN/ACK 给client端(源IP是server端的IP),该报文从OUTPUT hook点出去,由于SYN报文没有进行连接跟踪,并且设置了nf_conntrack_tcp_loose=0,因此SYNACK报文在会被连接跟踪设置为INVALID,不会创建CT(连接跟踪)。
      • 这里synack报文会将接收窗口设置为0,禁止client马上发送报文(如http request). 等SYNPROXY与server端的3次握手成功后,再发送ack给client更新接收窗口。
    • client端回应一个 TCP ACK,同理该报文也会被设置为INVALID,报文将会命中第二条规则,执行SYNPROXY动作,完成三次握手。
  • client端完成了与SYNPROXY的三次握手后, SYNPROXY将会马上自动与真实server端完成三次握手,SYNPROXY伪造一个SYN packet让真实server端以为client端在尝试与其连接:
    • SYNPROXY发送一个TCP SYN给真实server端,这是一个新的连接,该报文通过OUTPUT hook点进入netfilter,该报文会创建连接跟踪,状态为NEW。
    • SYNPROXY发送的SYN报文的源IP是client的源IP,目的IP是sever端的IP。
    • server端收到SYN报文后,发送一个SYN/ACK给client端,该报文会被SYNPROXY处理。
    • SYNPROXY收到来自server端的SYN/ACK报文后,将会回应一个ACK报文,CT的状态被标记为ESTABLISHED。
    • 一旦连接跟踪进入 ESTABLISHED状态,SYNPROXY将会让客户端与真实服务器直接通信。
  • 因此总共的握手过程有6次,如下图

         

  • tcp options的处理:
    • 由于SYNPROXY不知道真实服务器支持的tcp选项,所以在配置SYNPROXY的时候需要设置相应的TCP选项参数,这些参数一旦设置之后就不会变了,相当于常量。如上面的规则2设置了--sack-perm --timestamp --mss 1480 --wscale 7 --ecn五个选项。
    • synproxy向client回复的synack报文,将client的sack permit,ecn,wscale选项编码在timestamp中,然后会由ack报文带回来的synproxy,synproxy再解码出来,这样synproxy和server握手的时候,才能把client的sack permit,ecn,wscale选项透传给server。
      • 这样就要求synprox和client都支持tcp timestamp,才能正常的支持sack permit,ecn,wscale选项!
    • client的mss是编码在synack的seq中(syncookie),这样收到ack的时候,就可以从ack的ack_seq中还原出来。
  • seq(序号)转换:server端到client的tcp timestamp和tcp seq都需要经过synproxy的转换!因为SYNPROXY和server的初始seq号(即SYN/ACK报文的seq号)都是随机生成的。
    • synproxy发送syn报文给server的时候,会用client给SYNPROXY的ack报文的seq-1(就是syn报文的seq)作为发送给服务器的syn报文的发送序列号,那么请求方向就不需要进行序列号调整了。

 

6次握手的报文处理关键流程

- client -- SYN报文:
  - 先命中在prerouting的raw表的NOTRACK规则(优先级为NF_IP_PRI_RAW(-300)),设置skb->nfct = &nf_ct_untracked_get()->ct_general,skb->nfctinfo = IP_CT_NEW(见notrack_tg)。
  - 然后经过conntrack的处理(优先级为NF_IP_PRI_CONNTRACK(-200)),ipv4_conntrack_in()->nf_conntrack_in(),由于设置了notrack,因此这里会返回NF_ACCEPT,不会创建真正的连接跟踪ct,用的是skb->nfct = &nf_ct_untracked_get()->ct_general。
  - 接着进行路由判决,看是INPUT,还是FORWARD。
  - 之后到INPUT/FORWARD点,会命中filter表上的SYNPROXY规则(NF_IP_PRI_FILTER = 0),到synproxy_tg4()->synproxy_send_client_synack()回复SYNACK给client,这里返回NF_DROP,报文就不会往下走了,SYN报文处理结束。
  - SYN报文没有走到ipv4_synproxy_hook和ipv4_confirm()!
- synproxy -- SYNACK报文:
  - synproxy_send_client_synack() -> ip_local_out(),这里SYNACK的skb继承了SYN报文的ct,因此也是notrack的,不会创建真正的ct,及做nat等。
  - 会过postrouting。
  - 会走到ipv4_synproxy_hook(),由于nfct_synproxy()返回NULL,会返回NF_ACCEPT,没有做什么处理。
- client -- ACK报文:
  - 由于设置了tcp_loose==0,因此ACK报文会被nf_conntrack_in()->tcp_new()判断为INVALID的,不会创建ct,因此skb->ct == NULL。
  - 因此会在INPUT/FORWARD点,会命中filter表上的SYNPROXY规则(NF_IP_PRI_FILTER = 0),到synproxy_tg4()->synproxy_recv_client_ack()中发送SYN报文给server。
- synproxy:SYN报文:
  - synproxy_recv_client_ack()->synproxy_send_server_syn(),ct设置为&snet->tmpl->ct_general,状态为IP_CT_NEW,这样在ipv4_conntrack_local()->nf_conntrack_in()会根据这个模板创建ct,并且创建synproxy和seqadj的扩展。
  - 由于有创建了真正的ct,可以对SYN报文进行做nat。不会匹配到SYNPROXY的iptables规则。
  - 在postrouting点,会过ipv4_synproxy_hook()函数,这个时候ct的状态为TCP_CONNTRACK_SYN_SENT,会设置synproxy->isn = ntohl(th->ack_seq),等连接建立后用来初始化seqadj。
- server:synack报文:
  - 会找到ct。将ct的状态变成TCP_CONNTRACK_SYN_RECV
  - 走到LOCAL_IN点的ipv4_synproxy_hook(),synproxy_send_server_ack()发送ACK给server,nf_ct_seqadj_init()初始化seqadj,synproxy_send_client_ack()发送window update给client。
  - 返回NF_STOLEN,不再继续处理。
- client:发送报文(ACK或者数据报文):
  - prerouting会找到ct。
  - 走到LOCAL_IN/POSTROUTING 的 ipv4_synproxy_hook(优先级NF_IP_PRI_CONNTRACK_CONFIRM-1),synproxy_tstamp_adjust()进行timestamp转换。
  - 走到ipv4_confirm(优先级NF_IP_PRI_CONNTRACK_CONFIRM),进行seq转换。
0条评论
0 / 1000
rysf
5文章数
0粉丝数
rysf
5 文章 | 0 粉丝
原创

Linux内核SYNPROXY的使用及原理简介

2023-12-20 02:38:17
131
0

SYNPROXY是Linux内核基于连接跟踪实现的一个内核模块,可以用来缓解tcp syn flood攻击。

SYNPROXY扮演client端和server端的中间人(代理)角色,先和client端完成三次握手,确认三次握手成功后,才会和server端进行三次握手,这样就能有效的拦截掉无效的连接(如tcp syn flood攻击),避免浪费server端资源。

一、使用

可以通过下面的配置,对本机的tcp的80端口开启SYNPROXY。

# 禁止中间捡起连接,禁止client发送ack给synproxy的时候创建ct,这样ack才会命中第二条iptables规则,然后由synproxy模块处理
# The purpose of disabling loose tracking is to have the final ACK from the
# client not be picked up by conntrack, so it won't create a new conntrack
# entry and will be marked INVALID and also get directed to the target.
echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_loose

# 第一条iptables规则
# 不对需要做synproxy的流量进行连接跟踪,这个做法有两个作用:
# 1. 可以让需要做synproxy的报文命中下面那条iptables规则。
# 2. 连接跟踪由synproxy向server进行3次握手的时候创建,这样ipv4_synproxy_hook()函数才可以根
#    据连接跟踪的状态来判断要不要进行SYN报文的重传!
iptables -t raw -A PREROUTING -i eth0 -p tcp --dport 80 --syn -j NOTRACK

# 第二条iptables规则
# 将untracked、invalid的报文导到SYNPROXY模块来处理
# 如果server不是本机,那么规则要添加到FORWARD链,即"iptables -A FORWARD ...."
iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state UNTRACKED,INVALID \
    -j SYNPROXY --sack-perm --timestamp --mss 1480 --wscale 7 --ecn

# 第三条iptables规则
# 丢弃未被SYNPROXY处理的报文,这些报文认为是攻击包
iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state INVALID   -j DROP

二、原理

  • 当开启synproxy功能的时候,synproxy相对于客户端是透明的。三次握手首先是在客户端和synproxy之间进行:
    • client端发送TCP SYN 给server端。
    • 当报文到达防火墙的时候,通过上面的第一条iptables规则将其设置为UNTRACKED,那么该syn报文不会进行连接跟踪。
    • 这个UNTRACKED TCP SYN报文将会命中第二条规则,执行SYNPROXY动作。
    • SYNPROXY将会捕获该报文,记录报文中的相关信息,然后模仿server发送一个TCP SYN/ACK 给client端(源IP是server端的IP),该报文从OUTPUT hook点出去,由于SYN报文没有进行连接跟踪,并且设置了nf_conntrack_tcp_loose=0,因此SYNACK报文在会被连接跟踪设置为INVALID,不会创建CT(连接跟踪)。
      • 这里synack报文会将接收窗口设置为0,禁止client马上发送报文(如http request). 等SYNPROXY与server端的3次握手成功后,再发送ack给client更新接收窗口。
    • client端回应一个 TCP ACK,同理该报文也会被设置为INVALID,报文将会命中第二条规则,执行SYNPROXY动作,完成三次握手。
  • client端完成了与SYNPROXY的三次握手后, SYNPROXY将会马上自动与真实server端完成三次握手,SYNPROXY伪造一个SYN packet让真实server端以为client端在尝试与其连接:
    • SYNPROXY发送一个TCP SYN给真实server端,这是一个新的连接,该报文通过OUTPUT hook点进入netfilter,该报文会创建连接跟踪,状态为NEW。
    • SYNPROXY发送的SYN报文的源IP是client的源IP,目的IP是sever端的IP。
    • server端收到SYN报文后,发送一个SYN/ACK给client端,该报文会被SYNPROXY处理。
    • SYNPROXY收到来自server端的SYN/ACK报文后,将会回应一个ACK报文,CT的状态被标记为ESTABLISHED。
    • 一旦连接跟踪进入 ESTABLISHED状态,SYNPROXY将会让客户端与真实服务器直接通信。
  • 因此总共的握手过程有6次,如下图

         

  • tcp options的处理:
    • 由于SYNPROXY不知道真实服务器支持的tcp选项,所以在配置SYNPROXY的时候需要设置相应的TCP选项参数,这些参数一旦设置之后就不会变了,相当于常量。如上面的规则2设置了--sack-perm --timestamp --mss 1480 --wscale 7 --ecn五个选项。
    • synproxy向client回复的synack报文,将client的sack permit,ecn,wscale选项编码在timestamp中,然后会由ack报文带回来的synproxy,synproxy再解码出来,这样synproxy和server握手的时候,才能把client的sack permit,ecn,wscale选项透传给server。
      • 这样就要求synprox和client都支持tcp timestamp,才能正常的支持sack permit,ecn,wscale选项!
    • client的mss是编码在synack的seq中(syncookie),这样收到ack的时候,就可以从ack的ack_seq中还原出来。
  • seq(序号)转换:server端到client的tcp timestamp和tcp seq都需要经过synproxy的转换!因为SYNPROXY和server的初始seq号(即SYN/ACK报文的seq号)都是随机生成的。
    • synproxy发送syn报文给server的时候,会用client给SYNPROXY的ack报文的seq-1(就是syn报文的seq)作为发送给服务器的syn报文的发送序列号,那么请求方向就不需要进行序列号调整了。

 

6次握手的报文处理关键流程

- client -- SYN报文:
  - 先命中在prerouting的raw表的NOTRACK规则(优先级为NF_IP_PRI_RAW(-300)),设置skb->nfct = &nf_ct_untracked_get()->ct_general,skb->nfctinfo = IP_CT_NEW(见notrack_tg)。
  - 然后经过conntrack的处理(优先级为NF_IP_PRI_CONNTRACK(-200)),ipv4_conntrack_in()->nf_conntrack_in(),由于设置了notrack,因此这里会返回NF_ACCEPT,不会创建真正的连接跟踪ct,用的是skb->nfct = &nf_ct_untracked_get()->ct_general。
  - 接着进行路由判决,看是INPUT,还是FORWARD。
  - 之后到INPUT/FORWARD点,会命中filter表上的SYNPROXY规则(NF_IP_PRI_FILTER = 0),到synproxy_tg4()->synproxy_send_client_synack()回复SYNACK给client,这里返回NF_DROP,报文就不会往下走了,SYN报文处理结束。
  - SYN报文没有走到ipv4_synproxy_hook和ipv4_confirm()!
- synproxy -- SYNACK报文:
  - synproxy_send_client_synack() -> ip_local_out(),这里SYNACK的skb继承了SYN报文的ct,因此也是notrack的,不会创建真正的ct,及做nat等。
  - 会过postrouting。
  - 会走到ipv4_synproxy_hook(),由于nfct_synproxy()返回NULL,会返回NF_ACCEPT,没有做什么处理。
- client -- ACK报文:
  - 由于设置了tcp_loose==0,因此ACK报文会被nf_conntrack_in()->tcp_new()判断为INVALID的,不会创建ct,因此skb->ct == NULL。
  - 因此会在INPUT/FORWARD点,会命中filter表上的SYNPROXY规则(NF_IP_PRI_FILTER = 0),到synproxy_tg4()->synproxy_recv_client_ack()中发送SYN报文给server。
- synproxy:SYN报文:
  - synproxy_recv_client_ack()->synproxy_send_server_syn(),ct设置为&snet->tmpl->ct_general,状态为IP_CT_NEW,这样在ipv4_conntrack_local()->nf_conntrack_in()会根据这个模板创建ct,并且创建synproxy和seqadj的扩展。
  - 由于有创建了真正的ct,可以对SYN报文进行做nat。不会匹配到SYNPROXY的iptables规则。
  - 在postrouting点,会过ipv4_synproxy_hook()函数,这个时候ct的状态为TCP_CONNTRACK_SYN_SENT,会设置synproxy->isn = ntohl(th->ack_seq),等连接建立后用来初始化seqadj。
- server:synack报文:
  - 会找到ct。将ct的状态变成TCP_CONNTRACK_SYN_RECV
  - 走到LOCAL_IN点的ipv4_synproxy_hook(),synproxy_send_server_ack()发送ACK给server,nf_ct_seqadj_init()初始化seqadj,synproxy_send_client_ack()发送window update给client。
  - 返回NF_STOLEN,不再继续处理。
- client:发送报文(ACK或者数据报文):
  - prerouting会找到ct。
  - 走到LOCAL_IN/POSTROUTING 的 ipv4_synproxy_hook(优先级NF_IP_PRI_CONNTRACK_CONFIRM-1),synproxy_tstamp_adjust()进行timestamp转换。
  - 走到ipv4_confirm(优先级NF_IP_PRI_CONNTRACK_CONFIRM),进行seq转换。
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
1
1