深入分析在ISC BIND服務(wù)器中潛藏了15年的RCE漏洞
2020年10月,我們收到了一份針對ISC BIND服務(wù)器的匿名安全報告。在這份報告中發(fā)現(xiàn)的安全問題,實際上基于之前曝出的漏洞CVE-2006-5989,該漏洞影響Apache模塊mod_auth_kerb,并且,它最初也是由匿名研究人員發(fā)現(xiàn)的。ISC BIND服務(wù)器SPNEGO(the Simple and Protected GSSAPI Negotiation Mechanism,SPNEGO)組件內(nèi)共享了含有該漏洞的代碼,但ISC當時并沒有合并相應(yīng)的安全補丁。15年后,ISC對BIND中的這個漏洞進行了修復(fù),并為其分配了相應(yīng)的漏洞編號,即CVE-2020-8625。
對于BIND服務(wù)器來說,從9.11到9.16的版本都受到該漏洞的影響。并且,攻擊者可以在無需身份驗證的情況下遠程觸發(fā)該漏洞,進而導(dǎo)致一個4字節(jié)的堆溢出。這份安全報告的內(nèi)容符合Targeting Incentive Program的要求,但缺乏獲得全額獎金所需的完整exploit。不過,這仍然不失為一個優(yōu)秀的安全報告,而且這個漏洞也值得我們深入進行研究。
漏洞分析
該漏洞的成因,是位于lib/dns/spnego.c中的函數(shù)der_get_oid()存在堆溢出漏洞。
- static int
- der_get_oid(const unsigned char *p, size_t len, 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) {
- unsigned u = 0;
- do {
- --len;
- uu = u * 128 + (*p++ % 128);
- } while (len > 0U && p[-1] & 0x80);
- data->components[n] = u; // <-- (4)
- }
- // ...
- }
這個函數(shù)在(1)處分配一個數(shù)組緩沖區(qū)。變量len用于跟蹤緩沖區(qū)中剩余的元素數(shù)量。同時,代碼在(2)處對前2個元素進行了填充處理,但是,它在(3)處只將len減去了1。因此,循環(huán)(4)可以使緩沖區(qū)溢出1個元素。data->components的類型是int,所以,這將導(dǎo)致4字節(jié)的堆溢出。
觸發(fā)機制
由于該漏洞存在于SPNEGO組件中,因此,必須在BIND中對TKEY-GSSAPI進行相應(yīng)的配置。
- # 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 {
- type master;
- file "/etc/bind/example.nil.db";
- };
其中,dns.keytab文件位于bin/tests/system/tsiggss/ns1/中,而example.nil.db文件則是由腳本bin/tests/system/tsiggss/setup.sh生成的。
現(xiàn)在,相應(yīng)的測試環(huán)境已經(jīng)準備好了。當接收到一個手工請求時,該漏洞就會被觸發(fā),并產(chǎn)生以下調(diào)用棧:
- #0 der_get_oid at spnego.c:841
- #1 decode_oid at spnego.c:1054
- #2 decode_MechType at spnego_asn1.c:213
- #3 decode_MechTypeList at spnego_asn1.c:290
- #4 decode_NegTokenInit at spnego_asn1.c:523
- #5 gss_accept_sec_context_spnego at spnego.c:591
- #6 dst_gssapi_acceptctx at gssapictx.c:729
- #7 process_gsstkey at tkey.c:551
- #8 dns_tkey_processquery at tkey.c:882
- #9 ns_query_start at query.c:11315
- #10 ns__client_request at client.c:2161
- #11 processbuffer at tcpdns.c:227
- #12 dnslisten_readcb at tcpdns.c:294
- #13 read_cb at tcp.c:814
- ...
漏洞利用
這個漏洞的可利用性高度依賴于glibc的版本,而下面的解釋是基于Ubuntu18.04和glibc2.27的,后者支持tcache。
首先,我們要確定這個溢出漏洞所能控制的內(nèi)容:
- 在der_get_oid()中分配的易受攻擊的緩沖區(qū)的大小和內(nèi)容是可控的。順便說一下,當當前請求完成后,該緩沖區(qū)將被釋放。
- decode_MechTypeList()中有一個while循環(huán),用于重復(fù)執(zhí)行der_get_oid()函數(shù),并且循環(huán)次數(shù)也是可控的。
有了這兩點,我們就可以輕松地操縱堆了。為了準備堆,我們可以耗盡任意大小的tcache bins,并在請求完成后重新對其進行填充。同時,重新填充的分塊(chunk)在內(nèi)存中可以是連續(xù)的。這使得內(nèi)存布局相當有利于通過緩沖區(qū)溢出發(fā)動攻擊。
實現(xiàn)任意寫原語
在這個階段,通過濫用tcache空閑列表可輕松實現(xiàn)任意寫原語。
觸發(fā)一個4字節(jié)的溢出來擴展下一個空閑的chunk大小。
在下一個請求中,在受損的chunk中分配內(nèi)存空間。當請求結(jié)束時,它將被移動到新的tcache bin中。
用新的大小再次分配受損的chunk。這時,受損的chunk將與下一個空閑的chunk發(fā)生重疊,然后,用一個任意的值覆蓋其freelist。
從“中毒的”tcache freelist上分配內(nèi)存空間。它將返回一個任意地址。
泄漏內(nèi)存地址
默認情況下,會為BIND啟用所有Linux緩解措施。因此,我們首先要搞定ASLR,這意味著我們需要找到一種從內(nèi)存中泄漏地址的方法。一個可能實現(xiàn)內(nèi)存泄漏的機會,是利用code_NegTokenArg()函數(shù)。該函數(shù)用于將響應(yīng)消息編碼到一個緩沖區(qū)中,并將其發(fā)送給客戶端。
- static OM_uint32
- code_NegTokenArg(OM_uint32 *minor_status, const NegTokenResp *resp,
- unsigned char **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是一個臨時緩沖區(qū),它的初始大小是1024字節(jié),正好在tcache處理的范圍內(nèi)。而(6)處的outbuf是將被發(fā)送到客戶端的緩沖區(qū),其大小也在tcache的范圍內(nèi)。如果可以對這兩個緩沖區(qū)的大小進行tcache dup攻擊,那么,在(5)和(6)處的兩次malloc()調(diào)用將返回相同的地址。在執(zhí)行(7)處的free()函數(shù)之后,一個tcache->next指針將被更新到buf中,但是,這時它已經(jīng)和outbuf重疊在一起了。這意味著堆指針將泄露給客戶端。
理想情況下,位于(6)處的buf_len應(yīng)該選擇得足夠大,以避免干擾較小的tcache bins。不幸的是,最大值似乎只有96個字節(jié)。由于這個問題,進程根本無法存活,并在客戶端得到泄漏的堆指針后不久就會崩潰。因此,我們需要進行更深入的研究,以便來找到一種可以充分利用該漏洞的方法。
漏洞的修復(fù)
在BIND 9.16.12和BIND 9.11.28中,已經(jīng)修復(fù)了該漏洞。為了修復(fù)BIND 9.16,ISC完全放棄了SPNEGO的使用。在BIND 9.11中,他們針對原始問題應(yīng)用了補丁程序。
小結(jié)
這個安全漏洞表明,即使軟件是開源的,并且得到了廣泛使用,漏洞也會存在多年而難以被發(fā)現(xiàn)。軟件維護人員需要密切監(jiān)視他們使用的所有外部模塊,以確保應(yīng)用了最新的安全補丁。同時,該漏洞也表明這是一個非常棘手的挑戰(zhàn)。ISC BIND是Internet上最流行的DNS服務(wù)器,所以,該漏洞的影響范圍相當大,特別是該漏洞可以在遠程且無需身份驗證的情況下觸發(fā)。我們建議大家盡快更新相應(yīng)的DNS服務(wù)器。
本文翻譯自:https://www.thezdi.com/blog/2021/2/24/cve-2020-8625-a-fifteen-year-old-rce-bug-returns-in-isc-bind-server