Nginx暴露危漏洞CVE-2021-23017
日前著名Web服務(wù)器和反向代理服務(wù)器Nginx暴嚴(yán)重漏洞NS解析器Off-by-One堆寫入漏洞,該漏洞存在于Nginx的DNS解析模塊ngx_resolver_copy()。攻擊者可以利用該漏洞進(jìn)行遠(yuǎn)程DDos攻擊,甚至遠(yuǎn)程執(zhí)行。
概述
ngx_resolver_copy()在處理DNS響應(yīng)時(shí)出現(xiàn)一個(gè)off-by-one錯(cuò)誤,利用該漏洞網(wǎng)絡(luò)攻擊者可以在堆分配的緩沖區(qū)中寫一個(gè)點(diǎn)字符(.’, 0x2E)導(dǎo)致超出范圍。 所有配置解析器語(yǔ)法的(resolver xxxx)Nginx實(shí)例可以通過(guò)DNS響應(yīng)(響應(yīng)來(lái)自Nginx的DNS請(qǐng)求)來(lái)觸發(fā)該漏洞。 特制數(shù)據(jù)包允許使用0x2E覆蓋下一個(gè)堆塊元數(shù)據(jù)的最低有效字節(jié),利用該漏洞攻擊者,可以實(shí)現(xiàn)Ddos拒絕服務(wù),甚至可能實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行。
由于Nginx中缺乏DNS欺騙緩解措施,并且在檢查DNS事務(wù)ID之前調(diào)用了易受攻擊的功能,因此遠(yuǎn)程攻擊者可能能夠通向中毒服務(wù)器注入受毒的DNS響應(yīng)來(lái)利用此漏洞。
漏洞影響
嚴(yán)重等級(jí): 高
漏洞向量: 遠(yuǎn)程/DNS
確認(rèn)的受影響版本: 0.6.18-1.20.0
確認(rèn)的修補(bǔ)版本: 1.21.0,1.20.1
供應(yīng)商: F5,Inc.
狀態(tài): 公開(kāi)
CVE: CVE-2021-23017
CWE: 193
CVSS得分: 8.1
CVSS向量:CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H/E:U/RL:O/RC:C
漏洞分析
當(dāng)Nginx配置中設(shè)置resolver時(shí),Nginx DNS解析器(core/ngx_resolver.c)用于通過(guò)DNS解析多個(gè)模塊的主機(jī)名。
Nginx中通過(guò)ngx_resolver_copy()調(diào)用來(lái)驗(yàn)證和解壓縮DNS響應(yīng)中包含的每個(gè)DNS域名,接收網(wǎng)絡(luò)數(shù)據(jù)包作為輸入和指向正在處理的名稱的指針,并在成功后返回指向包含未壓縮名稱的新分配緩沖區(qū)的指針。調(diào)用整體上分兩步完成的,
- 計(jì)算未壓縮的域名大小的長(zhǎng)度len并驗(yàn)證輸入包的合法性,丟棄包含大于128個(gè)指針或包含超出輸入緩沖區(qū)邊界的域名。
- 分配輸出緩沖區(qū),并將未壓縮的域名復(fù)制到其中。
第1部分中的大小計(jì)算與第2部分中的未壓縮的域名之間的不匹配,導(dǎo)致一個(gè)len的一個(gè)off-by-one錯(cuò)誤,導(dǎo)致允許以一個(gè)字節(jié)為單位寫一個(gè)點(diǎn)字符超出name->data的邊界。
當(dāng)壓縮名稱的最后一部分包含一個(gè)指向NUL字節(jié)的指針時(shí),就會(huì)發(fā)生計(jì)算錯(cuò)誤。 盡管計(jì)算步驟僅考慮標(biāo)簽之間的點(diǎn),但每次處理標(biāo)簽并且接著的字符為非NUL時(shí),解壓縮步驟都會(huì)寫入一個(gè)點(diǎn)字符。當(dāng)標(biāo)簽后跟指向NUL字節(jié)的指針時(shí),解壓縮過(guò)程將:
- // 1) copy the label to the output buffer,
- ngx_strlow(dst, src, n);
- dst += n;
- src += n;
- // 2) read next character,
- n = *src++;
- // 3) as its a pointer, its not NUL,
- if (n != 0) {
- // 4) so a dot character that was not accounted for is written out of bounds
- *dst++ = '.';
- }
- // 5) Afterwards, the pointer is followed,
- if (n & 0xc0) {
- n = ((n & 0x3f) << 8) + *src;
- src = &buf[n];
- n = *src++;
- }
- // 6) and a NULL byte is found, signaling the end of the function
- if (n == 0) {
- name->len = dst - name->data;
- return NGX_OK;
- }
如果計(jì)算的大小恰好與堆塊大小對(duì)齊,則超出范圍的點(diǎn)字符將覆蓋下一個(gè)堆塊長(zhǎng)度的元數(shù)據(jù)中的最低有效字節(jié)。這可能會(huì)直接導(dǎo)致下一個(gè)堆塊的大小寫入,但還會(huì)覆蓋3個(gè)標(biāo)志,從而導(dǎo)致 PREV_INUSE被清除并 IS_MMAPPED被設(shè)置。
- ==7863== Invalid write of size 1
- ==7863== at 0x137C2E: ngx_resolver_copy (ngx_resolver.c:4018)
- ==7863== by 0x13D12B: ngx_resolver_process_a (ngx_resolver.c:2470)
- ==7863== by 0x13D12B: ngx_resolver_process_response (ngx_resolver.c:1844)
- ==7863== by 0x13D46A: ngx_resolver_udp_read (ngx_resolver.c:1574)
- ==7863== by 0x14AB19: ngx_epoll_process_events (ngx_epoll_module.c:901)
- ==7863== by 0x1414D4: ngx_process_events_and_timers (ngx_event.c:247)
- ==7863== by 0x148E57: ngx_worker_process_cycle (ngx_process_cycle.c:719)
- ==7863== by 0x1474DA: ngx_spawn_process (ngx_process.c:199)
- ==7863== by 0x1480A8: ngx_start_worker_processes (ngx_process_cycle.c:344)
- ==7863== by 0x14952D: ngx_master_process_cycle (ngx_process_cycle.c:130)
- ==7863== by 0x12237F: main (Nginx.c:383)
- ==7863== Address 0x4bbcfb8 is 0 bytes after a block of size 24 alloc'd
- ==7863== at 0x483E77F: malloc (vg_replace_malloc.c:307)
- ==7863== by 0x1448C4: ngx_alloc (ngx_alloc.c:22)
- ==7863== by 0x137AE4: ngx_resolver_alloc (ngx_resolver.c:4119)
- ==7863== by 0x137B26: ngx_resolver_copy (ngx_resolver.c:3994)
- ==7863== by 0x13D12B: ngx_resolver_process_a (ngx_resolver.c:2470)
- ==7863== by 0x13D12B: ngx_resolver_process_response (ngx_resolver.c:1844)
- ==7863== by 0x13D46A: ngx_resolver_udp_read (ngx_resolver.c:1574)
- ==7863== by 0x14AB19: ngx_epoll_process_events (ngx_epoll_module.c:901)
- ==7863== by 0x1414D4: ngx_process_events_and_timers (ngx_event.c:247)
- ==7863== by 0x148E57: ngx_worker_process_cycle (ngx_process_cycle.c:719)
- ==7863== by 0x1474DA: ngx_spawn_process (ngx_process.c:199)
- ==7863== by 0x1480A8: ngx_start_worker_processes (ngx_process_cycle.c:344)
- ==7863== by 0x14952D: ngx_master_process_cycle (ngx_process_cycle.c:130)
雖然目前還沒(méi)有Poc出來(lái),理論上該漏洞可以被用來(lái)進(jìn)行遠(yuǎn)程代碼執(zhí)行。
攻擊向量分析
DNS響應(yīng)可以通過(guò)多種方式觸發(fā)漏洞。
首先,Nginx必須發(fā)送了DNS請(qǐng)求,并且必須等待響應(yīng)。 然后,可以在DNS響應(yīng)的多個(gè)部分進(jìn)行投毒:
- DNS問(wèn)題QNAME,
- DNS回答名稱,
- DNS會(huì)回答RDATA以獲得CNAME和SRV響應(yīng),
- 通過(guò)使用多個(gè)中毒的QNAME,NAME或RDATA值制作響應(yīng),可以在處理響應(yīng)時(shí)多次擊中易受攻擊的函數(shù),從而有效地執(zhí)行多次脫機(jī)寫入。
此外,當(dāng)攻擊者提供中毒的CNAME時(shí),它將以遞歸方式解決,從而在執(zhí)行過(guò)程中觸發(fā)了額外的OOB寫操作 ngx_resolve_name_locked() 調(diào)用ngx_strlow()(ngx_resolver.c:594)和其他OOB讀取期間 ngx_resolver_dup()(ngx_resolver.c:790)和 ngx_crc32_short()(ngx_resolver.c:596)。
用于“example.net”請(qǐng)求的DNS響應(yīng)示例負(fù)載,其中包含被污染的CNAME:
稍微不同的有效負(fù)載(poc.py中的有效負(fù)載)填充了足夠的字節(jié)以覆蓋 next_chunk.mchunk_size帶點(diǎn)的最低有效字節(jié):
24字節(jié)的標(biāo)簽導(dǎo)致分配了24字節(jié)的緩沖區(qū),該緩沖區(qū)填充有24字節(jié)+一個(gè)超出范圍的點(diǎn)字符。
漏洞修復(fù)和解決
通過(guò)向域名解析時(shí),在域名末尾寫入的偽造的點(diǎn)字符分配一個(gè)額外的字節(jié)可以緩解此問(wèn)題。
受漏洞影響的配置
- daemon off;
- http{
- access_log logs/access.log;
- server{
- listen 8080;
- location / {
- resolver 127.0.0.1:1053;
- set $dns example.net;
- proxy_pass $dns;
- }
- }
- }
- events {
- worker_connections 1024;
- }