欢迎光临
我们一直在努力

BIND8安全漏洞分析-网管专栏,域名服务

建站超值云服务器,限时71元/月

对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 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]=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保护文件系统。
此外还可以使用其它一些方式保护自己的系统。

赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » BIND8安全漏洞分析-网管专栏,域名服务
分享到: 更多 (0)