利用TCP重传机制来玩端口敲门服务

TCP无法在连接建立之前进行认证,对于无连接的UDP而言,或者也将不能。

TCP有fastopen机制,但并不好用,本文的想法就是基于fastopen的,让第一个SYN包携带数据,然而又不能让它到达TCP层,必须在IP层完成认证。

于是顺势就有了所谓的敲门服务。

敲门的意义旨在避免非法连接的干扰,一来二去的,即便构不成DoS,服务端也需要对非法连接的客户端进行回应,这不但不安全,暴露了开放端口信息,也浪费了带宽资源。

互联网是一个开放的环境, 不要与陌生人说话! 默默地丢掉非法连接比回复一个RST要好很多。

本文来玩一个通过TCP重传机制来实现的敲门服务:
在这里插入图片描述

假设我们要保护SSH的22端口,首先我们在服务端进行以下配置:

# 以下配置部署在 192.168.56.101 这台Ubuntu上。
ipset create knockset hash:ip,port timeout 60 # 一分钟无活动即断开
iptables -t mangle -F
# 加入set的二元组允许建立连接
iptables -t mangle -A PREROUTING -p tcp --dport 22 -m set --match-set knockset src,src -j ACCEPT
# 过滤敲门包,将成功敲门的二元组加入ipset
iptables -t mangle -A PREROUTING -p tcp --dport 22 -m string --string "skinshoe" --algo bm --to 65535 -j SET --add-set knockset src,src
# 丢弃敲门包,因为正式包(也就是客户端重传的SYN包)马上就会到来
iptables -t mangle -A PREROUTING -p tcp --dport 22 -m string --string "skinshoe" --algo bm --to 65535 -j DROP
# 静默丢弃
iptables -t mangle -A PREROUTING -p tcp --dport 22 ! -s 192.168.56.1/32  -j DROP

下面是客户端一个基于Netfilter的模块:

// padding.c 部署在192.168.56.110上
#include <linux/module.h>
#include <net/netfilter/nf_conntrack.h>
#include <linux/netfilter/nf_conntrack_common.h>
#include <net/tcp.h>

int port = 22;
module_param(port, int, 0644);

char *templ = NULL;
module_param(templ, charp, 0);

unsigned int knock_out_hook(const struct nf_hook_ops *ops, struct sk_buff *skb,
                             const struct net_device *in, const struct net_device *out,
                             const struct nf_hook_state *state)
{
    
    
	struct iphdr *iph = ip_hdr(skb);
	struct tcphdr *th = NULL;
	enum ip_conntrack_info ctinfo;
	struct nf_conn *ct;
	unsigned int extra, len;
	char *cookie;

	if (templ == NULL)
		return NF_ACCEPT;

	ct = nf_ct_get(skb, &ctinfo);
	// 过滤一个连接的第一个SYN包。
	if (!ct || (ct->status & IPS_CONFIRMED))
		return NF_ACCEPT;

	if (iph->version != 4 || iph->protocol != IPPROTO_TCP)
		return NF_ACCEPT;

	iph = ip_hdr(skb);
	th = (struct tcphdr *)((unsigned char *)iph + (iph->ihl * 4));

	if (ntohs(th->dest) != port)
		return NF_ACCEPT;

	// 在一个连接的第一个SYN包后面padding我们的认证魔术字。
	cookie = kmalloc(32, GFP_ATOMIC);
	memcpy(cookie, templ, strlen(templ));
	extra = strlen(templ);
	skb_put(skb, extra);
	memcpy((char *)th + sizeof(struct tcphdr), cookie, extra);
	len = ntohs(iph->tot_len) + extra;
	iph->tot_len = htons(len);
	iph->check = 0;
	ip_send_check(iph);

	return NF_ACCEPT;
}

static struct nf_hook_ops knock_out_ops = {
    
    
	.hook     = knock_out_hook,
	.owner    = THIS_MODULE,
	.pf       = AF_INET,
	.hooknum  = NF_INET_LOCAL_OUT,
	.priority = NF_IP_PRI_LAST,
};

static int __init knock_init(void)
{
    
    
	if (nf_register_hook(&knock_out_ops) < 0) {
    
    
		return -1;
	}

	return 0;
}

static void __exit knock_exit(void)
{
    
    
	nf_unregister_hook(&knock_out_ops);
}

module_init(knock_init);
module_exit(knock_exit);
MODULE_LICENSE("GPL");

来来来,看效果:

# 不加载padding hook的时候:
[root@localhost ~]# ssh [email protected]
... # 无响应,抓包无内容,无回复,无RST

[root@localhost ~]# insmod ./padding.ko port=22 templ="skinshoe"
[root@localhost ~]# ssh [email protected]
[email protected]'s password:
Welcome to Ubuntu 19.10 (GNU/Linux 5.3.0-62-generic x86_64)
... # 成功连接

值得说明的是:

  • 本文只是一个POC,事实上可以完成很复杂的认证。
  • padding hook也并非一定要部署在客户端本机,它也可以成为一个单独的敲门代理。
  • 该方案独立于DDoS防护,这只是一个想法而已。
  • padding hook利用了conntrack,不要抬杠,conntrack并没有那么不堪,更何况它部署在客户端。
  • 如果将该POC思路用在网页上将可以实现防盗链,但我不懂前端技术,只能放弃。
  • 退一步讲,能不能不使用内核机制,答案是可以,你也可以直接用带外的UDP来完成敲门。

本来是想用systemtap写一个定向fork炸弹来逗运维,经理以及其手下的,没想到搞了这个,哎…


浙江温州皮鞋湿,下雨进水不会胖。

猜你喜欢

转载自blog.csdn.net/dog250/article/details/108479651