设为首页
加入收藏
帮助中心
首页 | 红盾通告 | 信息中心 | ASP技术 | 数据库 | 网页设计 | 网管专栏 | OICQ攻略 | 墨客频道 | 网站运营 |
当前位置:首页 >> 网管专栏 >> 域名服务 >> 正文
最新信息
·二级域名泛解析,让iis支持…
·iis如何绑定泛解析的域名
·如何利用自己的域名?
·域名注册完成后,要注意些什…
·域名发生冲突如何处理?
·注册一个什么样的域名好?
·Internet上域名命名的一般…
·什么是域名地址服务器(即D…
·谁是国际域名管理机构
·域名的结构
资料搜索
热点信息
·iis如何绑定泛解析的域名
·二级域名泛解析,让iis支持…
·域名的结构
·域名注册完成后,要注意些什…
·Internet上域名命名的一般…
·Linux DNS and BIND 服务器
·Win2000之DNS服务器的设置
·深入研究DNS原理
·分析DNS日志
·windows server 2003之DNS
推荐信息
·DNS服务器配置
·动态负载平衡DNS简介
·建立安全的DNS服务器
·主DNS服务器
·构建DNS服务器简易指南(2)
·构建DNS服务器简易指南(1)
·BIND8安全漏洞分析
·Linux DNS and BIND 服务器
·iptables应用之动态DNS
·域名中的国家代码


Google
 
BIND8安全漏洞分析
〖编辑:Cloudy | 浏览:人次〗

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


录入时间:2006-05-08 23:19:37 [打印本页] [关闭窗口] [返回顶部]
特别声明: 本站除部分特别声明禁止转载的专稿外的其他文章可以自由转载,但请务必注明出处和原始作者。文章版权归文章原始作者所有。对于被本站转载文章的个人和网站,我们表示深深的谢意。如果本站转载的文章有版权问题请联系编辑人员,我们尽快予以更正。

Copyright © 2006-2014 0733168.Com Inc All Rights Reserved
关于我们 | 广告合作 | 联系我们 | 法律声明 | 友情链接 | 意见反馈
本站所收录信息、社区话题、及本站所做之广告均属其个人行为,与本站立场无关
湘ICP备06008436号