Netfilter的expect连接的原理和利用
ip_conntrack有一个特性,那就是可以跟踪expect连接,所谓的expect连接,理解起来很简单,那就是“在一个连接中生成的另一个连接”,那么如何来识别一个连接要生成另一个连接呢?以FTP为例,FTP服务器会将文件传输所用的地址和端口信息作为数据载荷传输到对端的,Linux网关捕获这个数据包,将其解开然后根据FTP的协议规范获取地址和端口信息,随后就生成了一个expect连接。也就说,expect连接的参数是从数据载荷中得到的。
既然可以从数据载荷中得到一个“期望的连接”,那么随后的该期望的连接真正到来的时候一般是被允许通过的,这在防火墙上就是所谓的动态规则,在这里,一个约定就是防火墙本身对应用层协议是完全信任的,比方说FTP载荷中附带了生成expect连接的地址和端口信息,防火墙认为此信息是可信的,真的就是服务器或者客户端自己设置上去的。然而现实并不完美,这些信息可能是被攻击者硬添加进去的,如此一来,就有了绕过防火墙的可能,实现方式多种多样,最常见的就是包重放,攻击者截获一个包,然后在其载荷中按照一定的协议规范添加地址和端口信息,然后将此包重放在网络,当其经过防火墙的时候,防火墙就会生成一条动态的针对expect连接的允许规则,这样攻击者便可以绕过防火墙去访问本不该被访问的地址和端口了。
原理很简单,作为一个例子,我编写了一个内核模块,注册了一个捕获expect连接的helper(具体ip_conntrack的helper机制本文不再赘述,本质上就是一堆和既有显式ip_conntrack相关联的链表),模块代码如下:
- #include <linux/module.h>
- #include <linux/netfilter.h>
- #include <linux/ip.h>
- #include <net/tcp.h>
- #include <net/netfilter/nf_conntrack.h>
- #include <net/netfilter/nf_conntrack_expect.h>
- #include <net/netfilter/nf_conntrack_helper.h>
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Marywangran <[email protected]>");
- MODULE_DESCRIPTION("expect helper test");
- struct aa_proto {
- int type;
- int port;
- union nf_inet_addr addr;
- };
- static int aa_help(struct sk_buff *skb,
- unsigned int protoff,
- struct nf_conn *ct,
- enum ip_conntrack_info ctinfo)
- {
- unsigned int dataoff, datalen;
- const struct tcphdr *th;
- struct tcphdr _tcph;
- int ret;
- char *dt_ptr;
- struct nf_conntrack_expect *exp;
- int dir = CTINFO2DIR(ctinfo);
- struct aa_proto prot = {0};
- uint16_t port = ntohs((uint16_t)prot.port);
- char aa_buffer[512];
- if (ctinfo != IP_CT_ESTABLISHED
- && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
- return NF_ACCEPT;
- }
- //开始解析数据包的内容
- th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph);
- dataoff = protoff + th->doff * 4;
- datalen = skb->len - dataoff;
- dt_ptr = skb_header_pointer(skb, dataoff, datalen, aa_buffer);
- //将协议头复制下来
- memcpy(&prot, dt_ptr, sizeof(struct aa_proto));
- if (prot.type != 12) { //如果不是预定义的12类型,www.linuxidc.com说明不需要expect连接
- ret = NF_ACCEPT;
- goto out;
- }
- exp = nf_ct_expect_alloc(ct);
- port = ntohs((uint16_t)prot.port);
- nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, AF_INET,
- &ct->tuplehash[dir].tuple.src.u3, &prot.addr,
- IPPROTO_TCP, NULL, &port);
- if (nf_ct_expect_related(exp) != 0)
- ret = NF_DROP;
- else
- ret = NF_ACCEPT;
- out:
- return ret;
- }
- static const struct nf_conntrack_expect_policy aa_policy = {
- .max_expected = 10,
- .timeout = 50 * 60,
- };
- static struct nf_conntrack_helper aa = {
- .name = "aa",
- .me = THIS_MODULE,
- .tuple.src.l3num = AF_INET,
- //作用于TCP的12345端口
- .tuple.src.u.tcp.port = cpu_to_be16(12345),
- .tuple.dst.protonum = IPPROTO_TCP,
- .help = aa_help,
- .expect_policy = &aa_policy,
- };
- static void nf_conntrack_aa_fini(void)
- {
- nf_conntrack_helper_unregister(&aa);
- }
- static int __init nf_conntrack_aa_init(void)
- {
- int ret = nf_conntrack_helper_register(&aa);
- if (ret) {
- nf_conntrack_aa_fini();
- }
- return ret;
- }
- module_init(nf_conntrack_aa_init);
- module_exit(nf_conntrack_aa_fini);
- struct aa_proto {
- int type; //类型,如果是12则说明紧接着的端口,地址信息有效,需要初始化一个expect连接
- int port; //若有效,表示expect连接的目的端口
- union nf_inet_addr addr; //若有效,表示expect连接的目标地址
- };