2005年10月27日

所谓DNS服务器反向查询,就是输入IP地址,可以查出域名。某些 DNS 服务器支持的反向查询(iquery)功能可使攻击者获得区域传输。识别出注册到您的 DNS 服务器的每台计算机,并且可能被攻击者用来更好的了解您的网络。甚至当您在 DNS 服务器上禁用了区域传输时,iquery 功能仍旧可以允许区域传输发生。

在大部分的 DNS 搜索中,客户机一般执行正向搜索,正向搜索是基于存储在地址 (A) 资源记录中的另一台计算机的 DNS 名称的搜索。这类查询希望将 IP 地址作为应答的资源数据。

DNS 也提供反向搜索过程,允许客户机在名称查询期间使用已知的 IP 地址并根据它的地址搜索计算机名。反向搜索采取问答形式进行,如“您能告诉我使用 IP 地址 192.168.1.20 的计算机的 DNS 名称吗?”

DNS 最初在设计上并不支持这类查询。支持反向查询过程可能存在一个问题,即 DNS 名称空间组织和索引名称的方式及 IP 地址指派的方式不同。如果回答以前问题的唯一方式是在 DNS 名称空间中的所有域中搜索,则反向查询会花很长时间而且需要进行很多有用的处理。

为了解决该问题,在 DNS 标准中定义了特殊域 in-addr.arpa 域,并保留在 Internet DNS 名称空间中以提供实际可靠的方式来执行反向查询。为了创建反向名称空间,in-addr.arpa 域中的子域是通过 IP 地址带句点的十进制编号的相反顺序形式的。

因为与 DNS 名称不同,当 IP 地址从左向右读时,它们是以相反的方式解释的,所以对于每个八位字节值需要使用域的反序。从左向右读 IP 地址时,是从地址中第一部分的最一般信息(IP 网络地址)到最后八位字节中包含的更具体信息(IP 主机地址)。

因此,建立 in-addr.arpa 域树时,IP 地址八位字节的顺序必须倒置。这样安排以后,在向公司分配一组特定或有限的、且位于 Internet 定义的地址类别范围内的 IP 地址时,可为公司提供 DNS in-addr.arpa 树中的较低层分支的管理。

最后,在 DNS 中建立的 in-addr.arpa 域树要求定义其他资源记录 (RR) 类型,如指针 (PTR) RR。这种 RR 用于在反向搜索区域中创建映射,该反向搜索区域一般对应于其正向搜索区域中主机的 DNS 计算机名的主机 (A) 命名 RR。


注意

in-addr.arpa 域适用于所有根据网际协议版本 4 (IPv4) 寻址的 TCP/IP 网络。“新建区域”向导自动假定在创建新的反向搜索区域时您正在使用该域。
如果您正在为网际协议版本 6 (IPv6) 的网络安装 DNS 并配置反向搜索区域,则您可在“新建区域”向导中指定一个确切的名称。它允许您在可用于支持 IPv6 网络的 DNS 控制台中创建反向搜索区域,该网络使用不同的特殊域名,即 ip6.int 域。

其他信息可从 IPv6 和 DNS 上获得,包括有关如何创建和使用在 RFC 1886“支持 IP 版本 6 的 DNS 扩展名”中定义的 ip6.int 域名的范例。详细信息,请直接参考本 RFC 规范,可以从 RFC 的 Web 站点获得这一规范。

有关反向查询的更多信息,请参阅 RFC 1035“Domain Names – Implementation and Specification”,可从 http://www.rfc-editor.org/rfc/rfc1035.txt 获得该文章。(该文章为英文)

安全问题

DNS反向查询在很多的安全检测软件上,都被列为一个中风险的漏洞,因为通过反向查询,攻击者可以获得被攻击方的资料以及相应的网络资源,这对下一步攻击提供了便利。由于DNS反向查询是一种过时的方法,因此从安全角度考虑,建议DNS管理者关闭该功能。

本文部分资料来源于MS知识库和DNS的RFC文档

综述
现在随着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栈的信息,为下一步的单字节缓冲区攻击作好准备。

DNS服务有两个模式,一个是普通DNS,一个是根DNS。区别在于,普通DNS可以转发未正确解析的解析请求,而根DNS不转发请求。我来举个例子:假设我们需要解析163.com域名,客户计算机首先是按照本地设置的DNS服务器来向DNS服务器提交查询请求。这里是192.168.0.1,而该DNS服务器上显然没有该域名的注册资料,于是该DNS在一定延迟后,将DNS查询请求向转发器转发,直到14个根目录提示(可以在DNS服务器的属性里看到),根目录提示是用来查询网络上其他的DNS服务器用的,就是说,通过根目录提示可以查询到其他的DNS服务器,从而实现查询的请求转发,如果DNS被设置根DNS,则它对所属的区域有授权,换言之,就是不在响应对外的请求了。

在DNS的目录里,有很多的文件,其中root.dns就是根区域文件。如果DNS的根提示无效,或不能连接,则DNS就不能进行区域复制,也就是说,将不能响应本区域之外的解析请求。一般来说,如果DNS是网络上的第一个DNS,则被配置为根服务器。如果网络上没有其他的DNS,但仍需要解析INTERNET域名,则应使用默认的根目录提示(包含INTERNET DNS的名趁空间的授权根服务器列表)

INTERNET 根服务器为INTERNET域名空间树的指派授权机构。

在2000的DNS里,一个区域的建立时,可以选择区域的类型有三个:AD集成区域(区域保存在AD中,提供安全更新和集成存储,有关安全更新下面介绍),标准主要区域(区域保存在文本文件里,是区域的主要数据),标准辅助区域(现有主要区域的副本,用来帮助主服务器平衡处理工作量,并提供容错技术)。如果DNS上建立在域上的,建议使用第一个,如果是独立服务器,则应选择第2个。

安全更新:安全更新只适用于和DHCP结合的动态更新。只为AD集成区域提供。在有DHCP和DNS一起存在的环境中。有一个内置的组:dnsupdateproxygroup,这个组中包含所有的DHCP服务器,并授予所有DHCP服务器执行任何DHCP客户机的代理更新安全性权限。所谓动态更新是指许可DNS客户机在发生更改的任便和时候使用DNS服务器注册和动态的更新起资源记录,它有效的减少了对区域记录的手工管理,特别对频繁发生变化的DHCP客户机。