当前位置:Linux教程 - Linux - 对BIND几个缺陷的分析

对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]= |
    ---------------<---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栈的信息,为下一步的单字节缓冲区攻击作好准备。


    发布人:netbull 来自:LinuxAid