日期:2014-05-16  浏览次数:20814 次

Linux内核中FTP跟踪中的序列号处理漏洞
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,
严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
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,就是如何处理乱序数据包的问题,我想是在发