日前,著名Web伺服器和反向代理伺服器Nginx暴嚴重漏洞NS解析器Off-by-One堆寫入漏洞,該漏洞存在於Nginx的DNS解析模組ngx_resolver_copy()。攻擊者可以利用該漏洞進行遠端DDos攻擊,甚至遠端執行。
概述
ngx_resolver_copy()在處理DNS響應時出現一個off-by-one錯誤,利用該漏洞網路攻擊者可以在堆分配的緩衝區中寫一個點字元(.’, 0x2E)導致超出範圍。 所有配置解析器語法的(resolver xxxx)Nginx例項可以透過DNS響應(響應來自Nginx的DNS請求)來觸發該漏洞。 特製資料包允許使用0x2E覆蓋下一個堆塊元資料的最低有效位元組,利用該漏洞攻擊者,可以實現Ddos拒絕服務,甚至可能實現遠端程式碼執行。
由於Nginx中缺乏DNS欺騙緩解措施,並且在檢查DNS事務ID之前呼叫了易受攻擊的功能,因此遠端攻擊者可能能夠通向中毒伺服器注入受毒的DNS響應來利用此漏洞。
漏洞影響
嚴重等級: 高
漏洞向量: 遠端/DNS
確認的受影響版本: 0.6.18-1.20.0
確認的修補版本: 1.21.0,1.20.1
供應商: F5,Inc.
狀態: 公開
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
漏洞分析
當Nginx配置中設定resolver時,Nginx DNS解析器(core/ngx_resolver.c)用於透過DNS解析多個模組的主機名。
Nginx中透過ngx_resolver_copy()呼叫來驗證和解壓縮DNS響應中包含的每個DNS域名,接收網路資料包作為輸入和指向正在處理的名稱的指標,並在成功後返回指向包含未壓縮名稱的新分配緩衝區的指標。呼叫整體上分兩步完成的,
1)計算未壓縮的域名大小的長度len並驗證輸入包的合法性,丟棄包含大於128個指標或包含超出輸入緩衝區邊界的域名。
2)分配輸出緩衝區,並將未壓縮的域名複製到其中。
第1部分中的大小計算與第2部分中的未壓縮的域名之間的不匹配,導致一個len的一個off-by-one錯誤,導致允許以一個位元組為單位寫一個點字元超出name->data的邊界。
當壓縮名稱的最後一部分包含一個指向NUL位元組的指標時,就會發生計算錯誤。 儘管計算步驟僅考慮標籤之間的點,但每次處理標籤並且接著的字元為非NUL時,解壓縮步驟都會寫入一個點字元。當標籤後跟指向NUL位元組的指標時,解壓縮過程將:
// 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 functionif (n == 0) {name->len = dst - name->data;return NGX_OK;}
如果計算的大小恰好與堆塊大小對齊,則超出範圍的點字元將覆蓋下一個堆塊長度的元資料中的最低有效位元組。這可能會直接導致下一個堆塊的大小寫入,但還會覆蓋3個標誌,從而導致 PREV_INUSE被清除並 IS_MMAPPED被設定。
==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 0x1448C--4: 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)
雖然目前還沒有Poc出來,理論上該漏洞可以被用來進行遠端程式碼執行。
攻擊向量分析
DNS響應可以透過多種方式觸發漏洞。
首先,Nginx必須傳送了DNS請求,並且必須等待響應。 然後,可以在DNS響應的多個部分進行投毒:
DNS問題QNAME,
DNS回答名稱,
DNS會回答RDATA以獲得CNAME和SRV響應,
透過使用多箇中毒的QNAME,NAME或RDATA值製作響應,可以在處理響應時多次擊中易受攻擊的函式,從而有效地執行多次離線寫入。
此外,當攻擊者提供中毒的CNAME時,它將以遞迴方式解決,從而在執行過程中觸發了額外的OOB寫操作 ngx_resolve_name_locked() 呼叫ngx_strlow()(ngx_resolver.c:594)和其他OOB讀取期間 ngx_resolver_dup()(ngx_resolver.c:790)和 ngx_crc32_short()(ngx_resolver.c:596)。
用於“example.net”請求的DNS響應示例負載,其中包含被汙染的CNAME:
稍微不同的有效負載(poc.py中的有效負載)填充了足夠的位元組以覆蓋 next_chunk.mchunk_size帶點的最低有效位元組:
24位元組的標籤導致分配了24位元組的緩衝區,該緩衝區填充有24位元組+一個超出範圍的點字元。
漏洞修復和解決
透過向域名解析時,在域名末尾寫入的偽造的點字元分配一個額外的位元組可以緩解此問題。
受漏洞影響的配置
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;}
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;}
本文作者:ijzmesec