Glibc 是 GNU 发布的 LIBC 库的 C 运行库, Glibc 是 Linux 系统中最底层的 API ,基本其它任何运行库都会依赖于 Glibc 。 Glibc 除了封装 Linux 操作系统所提供的系统服务外,还提供了其它的必要服务的实现。由于 Glibc 几乎包含所有的 UNIX 通行的标准,可以说是操作系统重要支撑库。
Glibc 中的 DNS 解析器中存在基于栈的缓冲区溢出漏洞,当在程序中调用 Getaddrinfo 函数时,攻击者自定义域名或是通过中间人攻击利用该漏洞控制用户系统。比如攻击者向用户发送带有指向恶意域名的链接的邮件,一旦用户点击该链接,攻击者构造合法的 DNS 请求时、以过大的 DNS 数据回应便会形成堆栈缓存区溢出并执行远程代码,达到完全控制用户操作系统。该漏洞影响 Glibc 2.9 以后的所有版本,虽然可以进行远程执行攻击,攻击者还需要解决绕过 ASLR 系统安全机制。
Google提供的POC由两部分组成:
实测其它调用 Glibc 的程序也会因查询域名导致崩溃。伪造 DNS 服务器发出的 POC 数据,在 TCP DNS 数据中包含了大量字符 “B” , 如下 :
使用IDA远程调试Debian系统上的CVE-2015-7547-CLIENT,在调用Glibc的Getaddrinfo函数时出现崩溃,崩溃现场的状态如下:
由于产生溢出覆盖,EDX寄存器的值被控制为0x42424242,处在未使用的地址段,导致在对[EDX+3]进行寻址访问时造成异常。此时函数调用栈如下:
栈空间中被覆盖的数据如下:
Glibc 中导致此漏洞的函数调用顺序如下:
getaddrinfo (getaddrinfo.c) -> _nss_dns_gethostbyname4_r (dns-host.c) -> __libc_res_nsearch (res_query.c) -> __libc_res_nquery (res_query.c) -> __libc_res_nsend (res_send.c) -> send_vc (res_send.c)
getaddrinfo (getaddrinfo.c) -> _nss_dns_gethostbyname4_r (dns-host.c) -> __libc_res_nsearch (res_query.c) -> __libc_res_nquery (res_query.c) -> __libc_res_nsend (res_send.c) -> send_vc (res_send.c)
存在溢出漏洞的缓冲区是在_nss_dns_gethostbyname4_r函数中申请的。
可以看到在_nss_dns_gethostbyname4_r函数中,使用alloca函数申请了2048字节的内存空间。alloca函数的功能是动态开辟栈地址空间,但如果参数是个固定大小的值,汇编代码就生成为把ESP减去固定值。调试分析栈的布局可以发现,host_buffer等局部变量是处在栈的高地址,alloca分配的内存是处在栈的低地址,这2048字节被溢出之后会覆盖掉host_buffer等变量。
从以上两图可以看出,进入_nss_dns_gethostbyname4_r函数时,返回地址所在栈中的位置是0xBFFFF560。而当完成溢出覆盖导致访问异常时,此返回地址处的值已经被改写为0x42424242。
_nss_dns_gethostbyname4_r函数中调用了__libc_res_nsearch函数进行实际域名查询,把局部变量host_buffer的栈地址作为参数传递进去,用于保存DNS服务器数据的实际存储地址。最终会调用到send_vc函数,在接收大于2048字节的数据之前,本应该在判断缓冲区大小不够时去分配更大的堆内存,但由于存在一段不太成熟的测试代码结果造成了逻辑错误,使得判断缓冲区过小的条件永远不成立,这样就不会去分配大内存,导致数据保存到alloca分配的栈内存中,造成缓冲区溢出。在最新发布的glibc 2.23版补丁中,这段不成熟的代码已被删掉,解决了此漏洞。
POC导致程序崩溃的原因,是由于出现缓冲区溢出后,在__libc_res_nquery函数中会访问host_buffer指针所指向的地址,但此值已经被覆盖为0x42424242,是不可访问的地址,需要把这个值覆盖为一个可访问地址。
为了实现漏洞利用,要覆盖_nss_dns_gethostbyname4_r函数的返回地址。但是在此函数返回之前,还要进行一次free的操作。会判断host_buffer指针是否还是alloca分配的栈地址,如果被改变了,就说明又重新分配了堆内存,需要进行内存释放。但如果此变量被溢出覆盖成其它值了,就会导致释放这个非堆内存地址时,出现程序异常,不能继续加载返回地址。所以解决的办法是,在溢出覆盖后要么不改变这个指针的栈地址值,要么修改为一个有效的堆块起始地址。Glibc模块在函数代码中没有进行栈溢出检查,之后即可在函数返回时控制程序流程。
但是在开启地址随机化的情况下,如果没有办法泄露内存地址布局,单独靠这一漏洞是无法成功利用的。
由于glibc 2.9 是在2008年发行的,所以大量Linux 系统都会受到该漏洞影响。若一旦绕过内存防护技术,则该漏洞可以成为一大杀器。被劫持的DNS server进行中间人攻击,可直接批量获取大量主机权限。利用ldd 命令查看C 库函数版本如下:
建议广大用户尽快给操作系统打补丁,该漏洞存在于resolv/res_send.c文件中,当getaddrinfo()函数被调用时会触发该漏洞。技术人员可以限制TCP DNS响应包字节的大小,并丢弃超过512字节的UDP DNS数据包来缓解该问题。
有趣的是,早在去年的7月份,就有研究人员公布了有关这一漏洞的信息,但当时 此漏洞并没有得到重视。根据目前的调查情况我们认为此漏洞的级别该视为高危漏洞,glibc应用于众多Linux发行版本中,所以此类漏洞影响范围十分广泛。该漏洞影响glibc 2.9到2.22的所有版本。