BIND8安全漏洞分析

  对BIND几个缺陷的分析 综述 现在随着Internet的日益普及,而Internet非常依赖于域名服务(DNS)。在RFC845中对域名服务作了如下定义:一个迭代的分布式数据库系统,它为Internet操作提供了基本的信息,例如:域名<–>IP地址的相互转换,邮件处理信息。BIND(Berkeley Inetnet Name Domain,伯克利Internet域名是一种使用最广的域名系统。它有安全缺陷对Internet无疑于是一场灾难。 2001年月29日,Network Associates of California发表了一个报告,指出了BIND最近出现的四个安全缺陷。其中有两个是关于缓冲区溢出,可以使攻击者关闭DNS或者获得root权限,一个叫做"TSIG bug",影响BIND8,另一个是叫“complain bug"的缓冲区溢出缺陷,影响BIND4。其余两个一个叫做"infoleak",影响BIND4和BIND8,另一个叫做"complain bug"格式化字符串缺陷,只影响BIND4。本文将着重讲述infoleak和TSIG bug。其中,infoleak bug不能直接用来进行攻击,但是它可以泄露栈的信息,甚至使攻击者得到BIND运行时的内存布局,为使用TSIG进行攻击创造了便利。这恐怕也是最近两个蠕虫:lion和adore都使用BIND的漏洞进行传播的主要原因之一。 细节 1.infoleak infoleak bug是由Claudio Musmarra发现的,最早在CERT安全建议CA-2001-02对这个BUG进行了报道。它使攻击者能够直接得到named程序栈祯的信息,从而直接计算出进行单字节缓冲区溢出所需要的信息,大大增加了攻击的成功率。 程序执行时,在栈中保存了程序运行的内部变量和函数的局部变量,以及函数调用的返回地址等信息。infoleak bug可以使攻击者直接读出在栈中的这些信息,甚至程序运行时的内存布局。通过向运行有这个缺陷的BIND版本的DNS服务器发送一个特制的查询包,就可以达成上述目的。 所谓特制的查询包就是向一个合法的很大的IQUERY(反向查询)查询包。向一个运行BIND的DNS服务器发出一个合法的IQUERY请求,DNS服务器把应答记录放在这个查询包之后返回。应答包括一个域名、类型(type)、类别(class)和ttl(包的生存时间)。在构造这个反向查询包时,只要使域名对named程序的dn_skipname()函数是合法的就可以了。把这个反向查询包的数据长度设置为一个和很大的数值,就会是应答记录超出缓冲区的边界。named程序的req_iquery()函数会发现这个反向查询包非法,并且返回一个指示错误的字符串。不幸的是,它在检查是否有错误时,不管反向查询包的数据区有多长,首先把指向包尾的指针cp向后推,这样很可能使cp指针超出了缓冲区的边界。从req_iquery()函数返回后,ns_req()函数就会发出大小是cp-msg(指向缓冲区的头)个字节含有错误信息的应答包。如果这个应答包已经超出了缓冲区的大小,就会包含named程序当前栈祯的信息如ebp等等,然后攻击者就可以使用TSIG安全缺陷进行单字节缓冲区溢出攻击了。 因为相对于TSIG安全缺陷关于infoleak的分析资料较少,所以我将以bind-8.2为例对infoleak进行分析。BIND在查询包小于512个字节时,使用UDP/53端口接受数据(更详细的信息请参考TSIG部分),具体接受数据的函数就是datagram_read(),以下是datagram_read()函数的相关源代码 static void datagram_read(evContext lev, void *uap, int fd, int evmask) { interface *ifp = uap; struct sockaddr_in from; int from_len = sizeof from; int n, nudp; union { HEADER h; /* Force alignment of uf. */ u_char buf[PACKETSZ+1]; } u;<–这就是named函数存放小于512个字节的查询包的缓冲区,后面对于查询的处理操作都是针对于这个缓冲区的,也就是说,datagram_read是使用传址方式把查询包传递给以后的处理函数*/ ……………… dispatch_message(u.buf, n, PACKETSZ, NULL, from, fd, ifp); if (++nudp < nudptrans) goto more; } 这时,栈的布局如下: —————— |参数 | | | | | —————— | | | 返回地址 | —————— |ebp | —————— |各个局部变量 | —————– |u.buff[513] | —————– |u.buff[512] | —————– | ….. | —————– |u.buff[0] |<—-缓冲区 —————– 接着,dispatch_message函数调用ns_req()函数: void ns_req(u_char *msg, int msglen, int buflen, struct qstream *qsp, struct sockaddr_in from, int dfd) { HEADER *hp = (HEADER *) msg; u_char *cp, *eom;/*<—cp指向请求包的数据区*/ /*cp = msg + HFIXEDSZ*/ /*eom指向请求包的尾*/ /*eom = msg + msglen*/ ……………. if (error == NOERROR) { switch (hp->opcode) { case ns_o_query: action = req_query(hp, &cp, eom, qsp, &buflen, &msglen, msg, dfd, from, in_tsig); break; case ns_o_iquery: action = req_iquery(hp, &cp, eom, &buflen, msg, from); break; /*反向请求包由req_iquery函数处理*/ 此时,栈如图所示: —————— |参数 | | | | | —————— | | | 返回地址 | —————— |ebp | —————— |各个局部变量 | —————–<—-eom |u.buff[513] | —————– |u.buff[512] | —————– | ….. | —————– |u.buff[12] | —————–<—-cp | ….. | —————– |u.buff[0] | —————–<—msg 下面是req_iquery()函数: static enum req_action req_iquery(HEADER *hp, u_char **cpp, u_char *eom, int *buflenp, u_char *msg, struct sockaddr_in from) { int dlen, alen, n, type, class, count; char dnbuf[MAXDNAME], anbuf[PACKETSZ], *data, *fname; nameserIncr(from.sin_addr, nssRcvdIQ); if (ntohs(hp->ancount) != 1 || ntohs(hp->qdcount) != 0 || ntohs(hp->nscount) != 0 || ntohs(hp->arcount) != 0) { ns_debug(ns_log_default, 1, "FORMERR IQuery header counts wrong"); hp->qdcount = htons(0); hp->ancount = htons(0); hp->nscount = htons(0); hp->arcount = htons(0); hp->rcode = FORMERR; return (Finish); }/*构造包时,使其能够通过这些检查*/ /* * Skip domain name, get class, and type. */ if ((n = dn_skipname(*cpp, eom)) < 0) { ns_debug(ns_log_default, 1, "FORMERR IQuery packet name problem"); hp->rcode = FORMERR; return (Finish); } /*dn_skipname函数接着调用ns_name_skip函数*/ *cpp += n; /*使攻击程序构造的包数据区很大*/ 假设这时,栈如图所示: —————— |参数 | | | | | —————— | | | 返回地址 | —————— |ebp | —————— |各个局部变量 | —————–<—-eom |u.buff[512] | —————– |u.buff[511] | —————– | ….. | —————– |u.buff[446] | —————–<—-cp | … | —————– |u.buff[12] | —————– | ….. | —————– |u.buff[0] | —————–<—msg /*但是符合*cpp+3*INT16SZ+INT32SZ<=eom*/ if (*cpp + 3 * INT16SZ + INT32SZ > eom) { ns_debug(ns_log_default, 1, "FORMERR IQuery message too short"); hp->rcode = FORMERR; return (Finish); } /*named处理type,class*/ GETSHORT(type, *cpp); GETSHORT(class, *cpp); *cpp += INT32SZ; /* ttl */ GETSHORT(dlen, *cpp); /*cpp已经接近缓冲区的边界了*/ 此时,栈如图所示: —————— |参数 | | | | | —————— | | | 返回地址 | —————— |ebp | —————— |各个局部变量 | —————–<—-eom |u.buff[512] | —————– |u.buff[511] | —————– | …. | —————– |u.buff[458]=255| —————–<—cp |u.buff[457]=0 | —————– |u.buff[456]=255|<–假设dlen为255 —————– | ….. | —————– |u.buff[446] | —————– | … | —————– |u.buff[12] | —————– | ….. | —————– |u.buff[0] | —————–<—msg *cpp += dlen; /*攻击程序发出的反向查询包的dlen为一个很大的值*/ /*此时,再向后推dlen个字节*/ /*哈,越界了*/ if (*cpp != eom) { ns_debug(ns_log_default, 1, "FORMERR IQuery message length off"); hp->rcode = FORMERR; return (Finish); } 接下来,就是由ns_req()将cp-msg个字节发送给攻击程序。攻击者就可以得到named栈的信息,为下一步的单字节缓冲区攻击作好准备。 2.TSIG bug DNS域名服务器使用TISG(tranaction signature)来进行验证通讯。TSIG BUG因此而得名。在最近出现的四个BIND BUG中,TSIG BUG危害是最为严重的。一个TSIG是一个高层的DNS资源记录,在请求或者应答中是分别计算的,用完后丢弃,不能重复使用,也不应该保存在高速缓存中。TSIG是一个复杂的安全机制。它必须在消息的最后。如果在资源记录(RR)中有几个TSIG,或者位置不正确,BIND就会丢弃这个包并且送回一个错误信息。TSIG有几个验证机制,阻止了攻击者从网络上截取含有TSIG的包使用。 TSIG BUG影响的BIND版本有:8.2(any service pack),8.2.1,8.2.2(packs1-7),和所有的8.2.3beta版本。 当BIND接到一个请求,它会根据接受请求使用的传输机制,把请求放在栈或者堆中。如果DNS请求小于512个字节,它就使用UDP/53接受请求的数据,并将其放在栈区中;如果DNS请求大于512个字节,它就使用TCP/53接受请求的数据,并把请求数据放在堆中。 当请求小于或者等于512个字节时,由datagram_read()函数把请求放到栈中的一个513个字节缓冲区中,即u.buff;当收到一个TCP请求时,就由stream_getlen()函数把请求数据读到一个64K的缓冲区中,这个缓冲区叫做sp->s_buff,是在堆中为每个套接字分配的。其中,有一个很有意思的特征,无论是使用TCP传输协议还是UDP传输协议,BIND都是只在缓冲区中对数据进行操作,然后做出相应的响应。 BIND使用两个变量来跟踪缓冲区的使用情况:msglen保存缓冲区中现有数据的字节数;bufflen保存缓冲区没有使用的字节数。 当接到一个DNS消息,msglen被初始化为从网络上接到的数据的长度。对于UDP来说,就是由recvfrom()系统调用返回的数;而TCP消息的msglen是由客户端给出的。buflen被设置为读取消息的缓冲区的大小,UDP是512,TCP是64K。 通常情况下,在处理一个DNS查询时,BIND回在查询的后面加上应答、验证以及其它的记录信息。接着,BIND就会修改这个DNS查询的头来显示上面所做的修改,将其送出。在处理过程中,msglen将会反映构造应答信息的缓冲区使用情况,而buflen将跟踪缓冲区空闲区域的情况。在整个处理过程中,BIND都假设buflen+msglen等于缓冲区的长度。 根据消息头的设置,BIND会区分请求还是应答,分别对其进行处理。如果接到一个请求,它就区分这个请求是查询(query)、反向查询(iquery)、update还是notification。从BIND8.2开始,在处理请求数据之前,BIND首先要检验有没有TSIG,这个功能是由ns_find_tsig()来完成的,同时这个函数还会对TSIG的合法性进行基本的验证。如果有一个正确的TSIG而没有准确的security key,BIND就发出一个错误信号,并跳过正常的处理操作。此时,msglen和buflen也保留为其初始值,而不是被设置为其工作值。 因为有一个正确的TSIG但没有准确的security key,BIND就进行错误处理。在产生错误信息时,BIND会重新起用缓冲区并且在这个有问题请求之后加上一个TSIG。此时,BIND假定msglen+buflen等于缓冲区的大小,当然在通常情况下,这时对的,但是只是在通常情况下-:(,在一些特殊的情况下msglen+buflen几乎可以达到缓冲区大小的两倍。接下来,BIND就调用ns_sign()函数在查询之后加上一个TSIG,可能已经超出了缓冲区的范围。 下面是BIND处理请求的大体过程的示意图:(本来是应该使用HTML格式的,但—-是我一直对制作页面缺乏兴趣,所以只要如此了,不过我会抓紧学一学的-:) ) —————- |收到DNS请求 | —————- | | | | / / ————– ————— |UDP请求由 | |TCP请求由 | |datagram_read| |stream_getlen | |放到 | |放到 | ————– ————— | | | | / / ———— —————– |栈 | |堆heap | |u.buff[513]| | sp->s_buff | | | | | ———— —————— 跟踪变量:msglen—数据长度 buflen—未使用缓冲区的长度 图2.1 |—————————————————————-| | 接到一个DNS请求 | | msglen设置为从网络上接受的数据的长度 |buflen被初始化为缓冲区的长度| | UDP:recvfrom() |UDP:513 bytes | | TCP:由客户端给出 |TCP:64K bytes | |—————————————————————–| 判断请求是: query iquery update notification 检查是否有TSIG 并检验其合法性 ———————————————————————- 通常情况下, 异常情况下, TSIG和security key合法 正确的TSIG,而security key非法 处理请求 发出错误信号: BIND跳过正常对请求的处理 进行错误处理 在查询信息之后 msglen和buflen保留为其原来的值 加上验证及其它 记录 修改查询包的头 重用缓冲区产生错误信息 msglen等于数据的长度 假设nsglen+buflen等于原来缓冲区的长度 buflen空闲缓冲区的长度 假设原来缓冲区的长度等于 msglen+buflen 发出应答 加上TSIG,,溢出 —————————————————————— 例如,A公司的DNS使用的是BIND8.2.2-Patch5。一名攻击者通过端口扫描知道了相关的信息,接着攻击者向这台域名服务器发出了一个请求,而这个请求包有一个TSIG而security key非法。A公司的DNS收到这个请求进行处理,它发现一个TSIG但是没有合法的security key就发出了错误信号,而此时msglen和buflen都被锁定,不能在处理错误时准确反映缓冲区的情况。datagram_read()或者stream_getlen()函数返回之前,BIND在请求包之后加上了新的TSIG,就超出了缓冲区。 总结 针对这个BUG可以使用单字节缓冲区溢出和堆溢出exploit。由于这两个BUG和系统设置无关,所以应该赶快升级BIND系统。有一些途径可以用来对named进行保护: 1).不以root运行named; 2).使用chroot保护文件系统。 此外还可以使用其它一些方式保护自己的系统。 流转的时光,都成为命途中美丽的点缀,

BIND8安全漏洞分析

相关文章:

你感兴趣的文章:

标签云: