日期:2014-05-16 浏览次数:20883 次
1. 前言 在进行TCP多连接协议的跟踪时, 记录数据的序列号是重要的, 因为数据包可能因为丢失而重发, 如 果不记录特征数据点的序列号, 就可能出现重复定义期待子连接的情况而浪费资源。 Linux内核中对FTP跟踪处理中考虑了序列号问题,但有些问题,可使黑客通过打乱TCP数据包顺序使 跟踪失效,而跟踪失效可能会导致phrack63-0x13一文中的攻击生效。 以下Linux内核代码版本2.6.19.2。 2. FTP跟踪中的序列号处理 /* include/linux/netfilter/nf_conntrack_ftp.h */ #define NUM_SEQ_TO_REMEMBER 2 /* This structure exists only once per master */ // FTP主连接中记录相关信息的结构, 主要是记录期待的序列号 struct ip_ct_ftp_master { /* Valid seq positions for cmd matching after newline */ // 每个方向各保存2个序列号值, 可以容排序错误一次 u_int32_t seq_aft_nl[IP_CT_DIR_MAX][NUM_SEQ_TO_REMEMBER]; /* 0 means seq_match_aft_nl not set */ // 每个方向记录的序列号的数量 int seq_aft_nl_num[IP_CT_DIR_MAX]; }; 对于序列号处理定义了以下两个函数: /* net/ipv4/netfilter/ip_conntrack_ftp.c */ /* Look up to see if we're just after a \n. */ // 这个函数判断当前数据包的序列号是否是正在期待的序列号, 如果不是则跳过内容解析操作 static int find_nl_seq(u32 seq, const struct ip_ct_ftp_master *info, int dir) { unsigned int i; // 循环次数为该方向上记录的序列号的的数量 for (i = 0; i < info->seq_aft_nl_num[dir]; i++) // 如果当前数据包的序列号和期待的序列号中的任一个相同返回1 if (info->seq_aft_nl[dir][i] == seq) return 1; // 否则返回0表示失败,失败后虽然不解析包内容了,但仍然会调用下面的函数来调整 // 期待的序列号 return 0; } /* We don't update if it's older than what we have. */ // 这个函数更新主连接所期待的序列号, 更换最老的一个,但实际是失败的 // nl_seq是要期待的序列号 static void update_nl_seq(u32 nl_seq, struct ip_ct_ftp_master *info, int dir, struct sk_buff *skb) { unsigned int i, oldest = NUM_SEQ_TO_REMEMBER; /* Look for oldest: if we find exact match, we're done. */ // 循环次数为该方向上记录的序列号的的数量 for (i = 0; i < info->seq_aft_nl_num[dir]; i++) { // 如果当前数据包的序列号和期待的序列号相同则不用更新 if (info->seq_aft_nl[dir][i] == nl_seq) return; // 这个比较条件有问题, 当info->seq_aft_nl_num[dir]达到最大值(2)后 // oldest将永远赋值为0, 也就是两边各发出2个包后oldest就不变了 if (oldest == info->seq_aft_nl_num[dir] // 这个比较条件也几乎没有意义, oldest最大也就是2, 而info->seq_aft_nl表示序列号几乎不可能 // 小于2, 只有初始情况info->seq_aft_nl[dir][i]还为0是才可能为真, 其他基本永远为假 || before(info->seq_aft_nl[dir][i], oldest)) oldest = i; } // 调整期待的序列号 if (info->seq_aft_nl_num[dir] < NUM_SEQ_TO_REMEMBER) { info->seq_aft_nl[dir][info->seq_aft_nl_num[dir]++] = nl_seq; ip_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, skb); } else if (oldest != NUM_SEQ_TO_REMEMBER) { info->seq_aft_nl[dir][oldest] = nl_seq; ip_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, skb); } } 3. 漏洞描述 3.1 乱序包 从代码可知,如果数据包不是顺序发送的,而是乱序,而且错位到两个包,比如正常情况下依次发送 “PORT”(主动模式子连接端口信息)、“STOR”(上载)和“NOOP”(无操作)三个命令,但如果人为调 整发包的顺序,“STOR”,“NOOP”命令包先到防火墙,而“PORT”命令包最后到防火墙,这样由于 期待的序列号不对,防火墙就不解析PORT命令中的端口了,就不会建立期待的子连接,而这种乱序包 到目的服务器后服务器会自动排序而不会发生错误。注意, “PORT”,“STOR”,“NOOP”命令包 序列号都是正确的,只是到了数据链路层后特别改了发送顺序,使得“STOR”,“NOOP”包发到防火 墙,“PORT”后到防火墙。 防火墙不解析FTP命令中的子连接参数带来的问题在phrack63-0x13中进行了描述,本blog中也专门有 介绍(phrack63中关于穿透Linux防火墙的漏洞解决方法),最终可使外部访问FTP服务器的任意端口, 以前是通过不带“\r”的命令来使防火墙忽略解析,现在是通过乱序使其忽略解析,攻击方法重新说 明如下: 网络结构: Extranet (conntrack ftp,irc) Intranet hacker ----------------- FW --------------- FTP Server FW上的规则是只允许外到内的目的FTP21端口,允许内到外的目的IRC6667端口。 hacker按前面所说的方法进行文件上载操作,逆序先发送STOR和NOOP命令后发PORT命令,PORT命令中 的端口指定为6667(26,11),由于PORT命令后到,防火墙不解析子连接,FTP服务器的子连接以源端口 20,目的端口6667连接hacker,由于目的端口是6667进行IRC协议解析,而且该连接防火墙会认为这 是主连接而进行IRC解析,文件是hacker自己准备上载的,在文件中包含表示IRC子连接的“DCC CHAT ”数据,定义子连接的端口,这样防火墙就会建立一个期待的子连接允许hacker连接到FTP服务器的 任意端口。 攻击的难点:难点在于合法包的乱序发送,因此不能用系统自带的TCP栈,可能需要自己从底层编写 连接处理,或者能对指定的数据延迟发送,或者利用ip_queue,将包传到用户空间,STOR,NOOP等包 可以直接回内核,PORT包就延迟一下才返回内核;其次要求防火墙上需要打开内到外的多连接协议的 端口作为跳板。 3.2 NAT变长包 在NAT环境下,如果地址端口差别较大,这样长度就会发生变化,而系统记录的是发生了变化的序列 号,而与后续包的序列号不匹配,因此,如果连续发送两个“PORT”命令,后面那个PORT命令就不会 被解析。 不过我还没想出如何利用这个漏洞,不过正常情况不会发生同时发送两个“PORT”命令的情况,而且 攻击服务器是进行目的NAT,修改目的地址而不是源地址,因此“PORT”命令长度不发生变化,因此 这个漏洞不会影响安全,如何利用此漏洞还要好好想想。 3.3 oldest错误 设置oldest的目的应该是打算能同时保留NUM_SEQ_TO_REMEMBER个包的序列号,新包的序列号更新最 老的序列号,可惜算法还是有问题,两个各发送2个包后就始终只更新数组位置[0]的序列号了,位置 [1]的序列号不变。 4. 解决方法 对于3.1,就是如何处理乱序数据包的问题,我想是在发