2020年10月,我们收到了一份针对ISC BIND服务器的匿名安全报告。在这份报告中发现的安全问题,实际上基于之前曝出的漏洞CVE-2006-5989,该漏洞影响Apache模块mod_auth_kerb,并且,它最初也是由匿名研究人员发现的。ISC BIND服务器SPNEGO(the Simple and Protected GSSAPI Negotiation Mechanism,SPNEGO)组件内共享了含有该漏洞的代码,但ISC当时并没有合并相应的安全补丁。15年后,ISC对BIND中的这个漏洞进行了修复,并为其分配了相应的漏洞编号,即CVE-2020-8625。
对于BIND服务器来说,从9.11到9.16的版本都受到该漏洞的影响。并且,攻击者可以在无需身份验证的情况下远程触发该漏洞,进而导致一个4字节的堆溢出。这份安全报告的内容符合Targeting Incentive Program的要求,但缺乏获得全额奖金所需的完整exploit。不过,这仍然不失为一个优秀的安全报告,而且这个漏洞也值得我们深入进行研究。
漏洞分析
该漏洞的成因,是位于lib/dns/spnego.c中的函数der_get_oid()存在堆溢出漏洞。
staticint der_get_oid(constunsignedchar*p,size_tlen,oid*data,size_t*size){ //... data->components=malloc(len*sizeof(*data->components));//components==NULL){ return(ENOMEM); } data->components[0]=(*p)/40;//components[1]=(*p)%40; --len;//0U;++n){ unsignedu=0; do{ --len; uu=u*128+(*p++%128); }while(len>0U&&p[-1]&0x80); data->components[n]=u;//<--(4) } //... }
这个函数在(1)处分配一个数组缓冲区。变量len用于跟踪缓冲区中剩余的元素数量。同时,代码在(2)处对前2个元素进行了填充处理,但是,它在(3)处只将len减去了1。因此,循环(4)可以使缓冲区溢出1个元素。data->components的类型是int,所以,这将导致4字节的堆溢出。
触发机制
由于该漏洞存在于SPNEGO组件中,因此,必须在BIND中对TKEY-GSSAPI进行相应的配置。
#cat/etc/bind/named.conf.options options{ directory"/var/cache/bind"; tkey-gssapi-keytab"/etc/bind/dns.keytab"; }; #cat/etc/bind/named.conf.local zone"example.nil."IN{ typemaster; file"/etc/bind/example.nil.db"; };
其中,dns.keytab文件位于bin/tests/system/tsiggss/ns1/中,而example.nil.db文件则是由脚本bin/tests/system/tsiggss/setup.sh生成的。
现在,相应的测试环境已经准备好了。当接收到一个手工请求时,该漏洞就会被触发,并产生以下调用栈:
#0der_get_oidatspnego.c:841 #1decode_oidatspnego.c:1054 #2decode_MechTypeatspnego_asn1.c:213 #3decode_MechTypeListatspnego_asn1.c:290 #4decode_NegTokenInitatspnego_asn1.c:523 #5gss_accept_sec_context_spnegoatspnego.c:591 #6dst_gssapi_acceptctxatgssapictx.c:729 #7process_gsstkeyattkey.c:551 #8dns_tkey_processqueryattkey.c:882 #9ns_query_startatquery.c:11315 #10ns__client_requestatclient.c:2161 #11processbufferattcpdns.c:227 #12dnslisten_readcbattcpdns.c:294 #13read_cbattcp.c:814 ...
漏洞利用
这个漏洞的可利用性高度依赖于glibc的版本,而下面的解释是基于Ubuntu18.04和glibc2.27的,后者支持tcache。
首先,我们要确定这个溢出漏洞所能控制的内容:
- 在der_get_oid()中分配的易受攻击的缓冲区的大小和内容是可控的。顺便说一下,当当前请求完成后,该缓冲区将被释放。
- decode_MechTypeList()中有一个while循环,用于重复执行der_get_oid()函数,并且循环次数也是可控的。
有了这两点,我们就可以轻松地操纵堆了。为了准备堆,我们可以耗尽任意大小的tcache bins,并在请求完成后重新对其进行填充。同时,重新填充的分块(chunk)在内存中可以是连续的。这使得内存布局相当有利于通过缓冲区溢出发动攻击。
实现任意写原语
在这个阶段,通过滥用tcache空闲列表可轻松实现任意写原语。
触发一个4字节的溢出来扩展下一个空闲的chunk大小。
在下一个请求中,在受损的chunk中分配内存空间。当请求结束时,它将被移动到新的tcache bin中。
用新的大小再次分配受损的chunk。这时,受损的chunk将与下一个空闲的chunk发生重叠,然后,用一个任意的值覆盖其freelist。
从“中毒的”tcache freelist上分配内存空间。它将返回一个任意地址。
泄漏内存地址
默认情况下,会为BIND启用所有Linux缓解措施。因此,我们首先要搞定ASLR,这意味着我们需要找到一种从内存中泄漏地址的方法。一个可能实现内存泄漏的机会,是利用code_NegTokenArg()函数。该函数用于将响应消息编码到一个缓冲区中,并将其发送给客户端。
staticOM_uint32 code_NegTokenArg(OM_uint32*minor_status,constNegTokenResp*resp, unsignedchar**outbuf,size_t*outbuf_size){ //... buf_size=1024; buf=malloc(buf_size);//<--(5) //... do{ ret=encode_NegTokenResp(buf+buf_size-1,buf_size,resp, &buf_len); //... }while(ret==ASN1_OVERFLOW); *outbuf=malloc(buf_len);//<--(6) if(*outbuf==NULL){ *minor_status=ENOMEM; free(buf); return(GSS_S_FAILURE); } memmove(*outbuf,buf+buf_size-buf_len,buf_len); *outbuf_size=buf_len; free(buf);//<--(7) return(GSS_S_COMPLETE); }
位于(5)处的buf是一个临时缓冲区,它的初始大小是1024字节,正好在tcache处理的范围内。而(6)处的outbuf是将被发送到客户端的缓冲区,其大小也在tcache的范围内。如果可以对这两个缓冲区的大小进行tcache dup攻击,那么,在(5)和(6)处的两次malloc()调用将返回相同的地址。在执行(7)处的free()函数之后,一个tcache->next指针将被更新到buf中,但是,这时它已经和outbuf重叠在一起了。这意味着堆指针将泄露给客户端。
理想情况下,位于(6)处的buf_len应该选择得足够大,以避免干扰较小的tcache bins。不幸的是,最大值似乎只有96个字节。由于这个问题,进程根本无法存活,并在客户端得到泄漏的堆指针后不久就会崩溃。因此,我们需要进行更深入的研究,以便来找到一种可以充分利用该漏洞的方法。
漏洞的修复
在BIND 9.16.12和BIND 9.11.28中,已经修复了该漏洞。为了修复BIND 9.16,ISC完全放弃了SPNEGO的使用。在BIND 9.11中,他们针对原始问题应用了补丁程序。
小结
这个安全漏洞表明,即使软件是开源的,并且得到了广泛使用,漏洞也会存在多年而难以被发现。软件维护人员需要密切监视他们使用的所有外部模块,以确保应用了最新的安全补丁。同时,该漏洞也表明这是一个非常棘手的挑战。ISC BIND是Internet上最流行的DNS服务器,所以,该漏洞的影响范围相当大,特别是该漏洞可以在远程且无需身份验证的情况下触发。我们建议大家尽快更新相应的DNS服务器。
本文翻
转载请注明:IT运维空间 » 安全防护 » 深入分析在ISC BIND服务器中潜藏了15年的RCE漏洞
发表评论