新客网WWW.XKER.COM:致力做中国最专业的网络学院!
学院: 操作系统 - 网络应用 - 服务器 - 网络安全 - 工具软件 - 办公软件 - Web开发 - 数据库 - 网页设计 - 图形图像 - 媒体动画 - 硬件学堂 - 存储频道 - QQ专区
您的位置:首页 > 新闻中心 > 安全资讯 > 正文:MS07-029的教训:DNS RPC缓冲区溢出漏洞

MS07-029的教训:DNS RPC缓冲区溢出漏洞

新客网 XKER.COM 2007-08-30 来源: Michael Howard/周希 译 收藏本文

 

 

本文通过在代码层分析一类漏洞形成的原因,以提醒读者注意,并一道讨论解决这类问题的方法。

读者可能已经注意到了,微软最近公布了一个安全更新是关于DNS服务器代码漏洞的,这里先花一点时间介绍一下这个漏洞。

哪些产品受影响

这个DNS漏洞只对Windows的服务器平台有影响,WindowsXP和Vista用户丝毫不用担心,因为这种客户操作系统不包含DNS服务器代码,只有DNS客户端代码。DNS服务器默认情况下是关闭的,除非安装的是中小企业版或计算机被配置成活动目录控制器,这样DNS服务器才会默认打开。Windows2000、2003、长角服务器beta2都受到影响。

该漏洞的本质

该漏洞出现在RPC监听端口代码段,该端口是为DNS服务器管理所用的,反而在DNS服务主线程监听53端口的编码上没有出现问题。自从Windows XP SP2以来,微软就是用RPC通讯进行默认认证,这是个直接教训,在Blaster蠕虫中以领教。这里更有趣的是RPC端点的匿名访问方式。对于其本身来讲,匿名访问不能仅仅称之为漏洞,而应该说就是一种攻击后门,可导致大范围攻击。

代码

该漏洞出现在一个结构中的堆栈,如下:

 

typedef struct _CountName {

    UCHAR   Length;

    UCHAR   LabelCount;

    CHAR    RawName[ DNS_MAX_NAME_LENGTH+1 ]; 

} COUNT_NAME, *PCOUNT_NAME;

该结构的一个指针被当作DNS主机名参数传递到下面的函数,如果PCOUNT_NAME->RawName溢出的话,非信任的代码就会出现在如下函数的第二个参数中。

 

DNS_STATUS Name_ConvertFileNameToCountName(

    PCOUNT_NAME     pCountName,

    PCHAR           pchName,

    DWORD           cchNameLength) {

 

    PCHAR       pch;

    UCHAR       ch;

    PCHAR       pchstartLabel;      // ptr to start of label

    PCHAR       pchend;             // ptr to end of name

    PCHAR       presult;

    PCHAR       presultLabel;

    PCHAR       presultMax;

    WORD        charType = 0;

    WORD        maskDowncase;

    DNS_STATUS  status;

    INT         labelLength;        // length of current label

    UCHAR       labelCount = 0;

 

    //  result buffer, leave space for label

    presultLabel = presult = pCountName->RawName;

    presultMax = presult + DNS_MAX_NAME_LENGTH;

    presult++;

 

    //  Character selection mask

    //      '\' slash quote

    //      '.' dot label separator are special chars

    //      upper case must be downcased

    //      everything else is copied

    maskDowncase = B_UPPER;

 

    //  setup start and end ptrs and verify length

    pchstartLabel = pch = pchName;

    if ( !cchNameLength )

        cchNameLength = strlen( pch );

 

    pchend = pch + cchNameLength;

 

    while ( pch ) {

 

        if ( pch >= pchend ) {

            ch = 0;

            charType = FC_NULL;

        }

 

        ...

 

        //  downcase upper case

        if ( charType & maskDowncase ) {

            //  if name exceeds DNS name max => invalid

            if ( presult >= presultMax )

                goto InvalidName;

 

            *presult++ = DOWNCASE_ASCII(ch);

            continue;

        }

 

        if ( charType & B_DOT ) {

            //  verify label length

            labelLength = (int)(presult - presultLabel - 1);

 

            if ( labelLength > DNS_MAX_LABEL_LENGTH )

                goto InvalidName;

 

            //  set label count in result name

            *presultLabel = (CHAR)labelLength;

            presultLabel = presult++;

 

            if ( pch >= pchend ) {

                if ( labelLength != 0 ) {

                    labelCount++;

                    *presultLabel = 0;

                    break;

                }

 

                presult--;

                break;

            }

 

            //  set up for next label

            if ( labelLength != 0 ) {

                labelCount++;

                continue;

            }

 

            goto InvalidName;

        }

 

        //  quoted character

        //      - single quote just get next char

        //      - octal quote read up to three octal characters

        else if ( ch == SLASH_CHAR ) {

            //  if name exceeds DNS name max => invalid

            if ( presult >= presultMax )

                goto InvalidName;

 

            << 注意:presult的长度未作限制 >>

      << extractQuotedChar 重写了 pch >>

            pch = extractQuotedChar(

                    presult++,

                    pch,

                    pchend );

        }

    }

我们可以看到,代码中有多处边界检查,但对于决定pch大小的重要变量presult却未加限制,因此就造成了该漏洞。教训啊,少检查一个都不行。

分析工具

一般的静态溢出代码分析工具找不出这种错误,因为该结构的本质就是超限的:

 

typedef struct _CountName {

    UCHAR   Length;

    UCHAR   LabelCount;

    CHAR    RawName[ DNS_MAX_NAME_LENGTH+1 ]; 

} COUNT_NAME, *PCOUNT_NAME;

我们来看结构中最后一个变量,是一段缓冲区。有一些结构遵循“放在缓冲区后”原则,例如安全标识(SID)结构:

 

typedef struct _SID {

   BYTE  Revision;

   BYTE  SubAuthorityCount;

   SID_IDENTIFIER_AUTHORITY IdentifierAuthority;

   DWORD SubAuthority[ANYSIZE_ARRAY];

} SID, *PSID;

SID结构的最后一个变量是由DWORD组成的缓冲区,并且ANYSIZE_ARRAY初始化会设置成1。这就是通常所说的可变长数组,一般的静态分析工具遇到这种情况,就会有针对性的改变搜寻方式来查找bug,然而PCOUNT_NAME->RawName是固定长度数组,分析工具就不会对其进行有针对性的分析。简而言之,利用可变长数组和静态分析工具就可以检查到这种bug。

Fuzz测试

因为这是一个管理员权限的接口,只需要执行了最小的RPC Fuzz测试,虽然没有发现该漏洞,那是因为判断RPC端点是否需要认证。要提供一套接口,分析和测试是非常重要的,如果是可以远程匿名访问的接口,就一定要好好检查,其比本地管理接口出问题的可能性要大很多。要注意Fuzz测试也就这么大能耐了,其很有效,但对于发现所有漏洞来说,还远远不够。

操作系统防御

Windows2000基本没有任何防御,没有防火墙、没有/GS(编译时缓冲区安全检查指令)、没有DEP/NX(代码执行防护)和ASLR(地址空间布局随机化),利用代码漏洞很容易。

Windows2003是使用/GS、DEP/NX编译的,但是没有ASLR,自带有个防火墙,但默认没开。而且由于编码的原因,即使编译时打开/GS,也漏掉了很多bug。

Windows长角服务器和现在的Windows2008,是以/GS方式编译,链接是采用/SafeSEH和DEP/NX以及ASLR,使得堆栈随机分布。同时自带防火墙,这种组合防御增加了安全性。

另一个重要的防御措施是服务重启策略,这个策略和ALSR一道增加缓冲区溢出攻击难度,因为ALSR使得攻击需要多次尝试,而服务重启策略限定了尝试次数。

总结

通过研究DNS RPC漏洞,我们要对RPC端点匿名认证多加小心,同时在缓冲区溢出检查时,注意可变长数组的用法。

收藏】 【评论】 【推荐】 【投稿】 【打印】 【关闭
发表评论
要记得去论坛讨论,点击注册新会员匿名评论
评论内容:不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
阅读排行
随机推荐
实用信息推荐