解读 | 一个 Linux 漏洞火了,什么情况?严重么?

1,761 阅读7分钟
原文链接: www.sdk.cn

摘要:昨天,一个Linux的漏洞火了,很多同学可能要睡不好了,这事儿究竟有多么严重?网上披露“Linux底层函数库glibc出现重大安全漏洞”、“多个 Linux 发行版均受影响”,主流的Linux发行版系统都受到波及。

昨天,一个Linux的漏洞火了,很多同学可能要睡不好了,这事儿究竟有多么严重?网上披露“Linux底层函数库glibc出现重大安全漏洞”、“多个 Linux 发行版均受影响”,主流的Linux发行版系统都受到波及。

在这里给大家打个定心针,腾讯云专家在第一时间接到这一消息,迅速组成专家团队当天赶制修复方案告知腾讯云上的客户修复,同时我们将在这里公布修复方案,供业界所有同学参考

◆◆◆

这个漏洞到底是怎么一回事?

我们先来看看外部报道是怎么说的:

◆◆◆ 

这个漏洞到底有多严重?

可以说,只要系统是Linux的童鞋,在使用公网DNS服务时,如果DNS服务器被劫持,就有可能被远端的攻击者通过网络攻破系统,执行黑客自己的程序。

◆◆◆

具体的攻击方式是这样的

Glibc中处理DNS查询的代码中存在栈溢出漏洞,远端攻击者可以通过DNS服务回应特定构造的DNS响应数据包,导致glibc相关的应用程序crash或者利用栈溢出运行任意代码。应用程序调用使用getaddrinfo 函数将会收到该漏洞的影响。该漏洞编号CVE-2015-7547

◆◆◆

受影响的系统有哪些?

CentOS6 所有版本

CentOS7 所有版本

SUSE Linux Enterprise Server 11 SP3

SUSE Linux Enterprise Server 12

Ubuntu Server 14.04.1 LTS 32位

Ubuntu Server 14.04.1 LTS 64位

Ubuntu Server 12.04 LTS 64位

Debian 8.2 32位

Debian 8.2 64位

Debian 7.8 32位

Debian 7.8 64位

Debian 7.4 64位

CoreOS 717.3.0 64位

◆◆◆

如何修复该漏洞?

修复方法请点击文末的“阅读原文”获取。

PS: 目前腾讯云提供的基础操作系统镜像 CentOS系列、Ubuntu 系列、Debian 系列都已修复该问题,新创建的云服务器不存在该漏洞。腾讯云提供的更新源已经提供了修改该漏洞的glibc版本,对于已经运行的云服务器可以通过手动更新glibc修复。 

◆◆◆

深度剖析:这个漏洞具体是如何触发的?

要搞清楚漏洞的产生,我们需要从代码层面分析一下这个漏洞。

首先从getaddrinfo函数开始,getaddrinfo函数在resolv/nss_dns/dns-host.c中,调用了alloca 在栈上分配了2048个字节。

  host_buffer.buf = orig_host_buffer = (querybuf *) alloca (2048);   

  int n = __libc_res_nsearch (&_res, name, C_IN, T_UNSPEC,

      host_buffer.buf->buf, 2048, &host_buffer.ptr,

      &ans2p, &nans2p, &resplen2);

alloca 函数在栈分配2048个字节,用于存放DNS服务器的响应数据。函数还传递了ans2p,nans2p,resplen2三个参数,存放DNS服务器的响应数据。ans2p用于返回存放第二份响应数据包缓冲区地址,nans2p返回存放第二份响应数据包的扩充缓冲区的大小,resplen2返回第二份响应数据包数据包的大小。函数的返回值是第一份响应数据包的大小。

getaddrinfo函数如果传递的是AF_UNSPEC参数的话,会同时进行IPv4,IPv6的查询,分配组建IPv4和IPv6的数据包发送和接受。

__libc_res_nsearch继续调用__libc_res_nquery函数。__libc_res_nquery在调用__libc_res_nsend函数。__libc_res_nsend用来发送和接受DNS相关的数据包。

__libc_res_nsend(statp, query1, nquery1, query2, nquery2, answer,

     anslen, answerp, answerp2, nanswerp2, resplen2)

__libc_res_nsend中会调用send_vc和send_dg函数和DNS服务器交互,TCP场景下调用send_vc,UDP的话调用send_dg。本文以send_dg为例进行分析。

send_dg(statp, buf, buflen, buf2, buflen2,

    &ans, &anssiz, &terrno,

    ns, &v_circuit, &gotsomewhere, ansp,

    ansp2, nansp2, resplen2)

send_dg首先调用__sendmmsg发送buf, buflen, buf2, buflen2中的数据包到DNS服务器。buf, buflen, buf2, buflen2是对应的查询消息。再调用recvfrom接受从DNS的响应包,而问题就出现在这段代码。

首先分析send_dg函数中recvfrom的使用使用thisansp变量标识接受数据缓冲区的地址,使用thisanssizp变量标识接受数据缓冲区的大小。

*thisresplenp = recvfrom(pfd[0].fd, (char*)*thisansp,

 *thisanssizp, 0,

(struct sockaddr *)&from, &fromlen);

继续分析send_dg对thisansp,thisanssizp变量的处理逻辑。

第一次收到数据包,使用之前在栈上分配的2048个字节,代码处理如下:

if ((recvresp1 | recvresp2) == 0 || buf2 == NULL) {

thisanssizp = anssizp;

thisansp = anscp ?: ansp;

assert (anscp != NULL || ansp2 == NULL);

thisresplenp = &resplen;

当第二次收到数据包时,处理如下:

if (*anssizp != MAXPACKET) {

/* 判断第一个缓冲区长度anssizp 是否是65536,如果不是,表示2048个缓冲区在接受第一个数据包的时候足够,因此第二个缓冲区继续使用2048个缓冲区的剩余部分*/

*anssizp2 = orig_anssizp - resplen;

*ansp2 = *ansp + resplen;

} else {

/* anssizp 等于65536,说明之前的接受数据包大于2048个字节,栈中分配的空间不足以存放,但是第二次查询的数据有可能小于2048,因此尝试使用栈中的内存保存响应包*/

*anssizp2 = orig_anssizp;

*ansp2 = *ansp;

              /*修改thisanssizp thisansp thisresplenp,分别表示调用recvfrom函数接受缓冲区的大小,接受缓冲区地址,接受到的数据包长度*/

thisanssizp = anssizp2;

thisansp = ansp2;

thisresplenp = resplen2;

    if (*thisanssizp < maxpacket< p="">

    /* 判断接受的数据包,thisanssizp 是否足够存放,如果不够的话,调用malloc 从堆上分配65536个字节,用来存放接受到的数据包*/

    && anscp

    && (ioctl (pfd[0].fd, FIONREAD, thisresplenp) < 0< p="">

|| *thisanssizp < *thisresplenp)) {< p="">

u_char *newp = malloc (MAXPACKET);

if (newp != NULL) {

*anssizp = MAXPACKET;    /*修改anssizp 变量表示已经从堆上分配了内存*/

*thisansp = ans = newp;     /*修改thisansp 变量,本地recvfrom使用新分配的内存进行存放*/

而问题就出现在这段代码上,存在两个问题:

1.在使用新分配的内存是,修改了thisansp变量,但是没有修改thisanssizp 变量为新分配的malloc内存的大小。

2.更新了anssizp 标识第一个缓存区的大小,但是没有更新ansp变量,ansp还是指向之前在栈上分配的2048个字节。

考虑如下场景:

* 第一次的DNS响应数据包是2048,正好使用完了在栈上分配的2048个字节.。

* 接受第二次DNS响应数据包,由于之前第一个数据包已经使用完2048个字节,所以代码会走到malloc流程从堆上分配内存,但是由于前面提到的bug,thisanssizp 没有被更新,而thisanssizp 在这种场景下为0,会导致recvfrom返回失败,导致send_dg直接退出。这个时候,ansp指向栈上的2048个内存区,但是anssizp被修改为65536。

* send_dg再次被调用,这个时候接受第三个DNS响应数据包,ansp指向栈上的2048个内存区,anssizp被修改为65536. 这个时候如果接受超过2048个数据包,会导致栈溢出。因此攻击者可以构造一个65536的数据包,前面2048个字节是正规的DNS数据,后面63487 个字节利用栈溢出执行自己的代码。

◆◆◆ 

触发该漏洞的触发条件:

* 程序调用getaddrinfo函数,传递AF_UNSPEC参数进行DNS查询。

* DNS服务器第一次响应2048字节的数据包。

* DNS服务器响应第二个数据包,触发send_dg出错退出.

* DNS服务器响应第三个数据包,该数据包可以被攻击者利用,前2048包含有效的DNS响应数据包,后63487个字节可以作为栈溢出漏洞利用,执行攻击者自己的代码。