- 论坛徽章:
- 0
|
请教:如何关闭"DNS Support IQUERY"漏洞
现在随着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 \'buf\'. */ 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]= | ---------------<---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栈的信息,为下一步的单字节缓冲区攻击作好准备。 |
|