這幾天有點忙,CVE-2020-0796出來了,沒靜下心來關注一下,顯得太不尊重這個漏洞了,今天周末,關注一下,水一篇。
一、漏洞描述
漏洞公告顯示,SMB 3.1.1協議中處理壓縮消息時,對其中數據沒有經過安全檢查,直接使用會引發內存破壞漏洞,可能被攻擊者利用遠程執行任意代碼。攻擊者利用該漏洞無須權限即可實現遠程代碼執行,受黑客攻擊的目標系統只需開機在線即可能被入侵。
二、漏洞危害等級
高
三、影響版本
Windows 10 Version 1903 for 32-bit Systems
Windows 10 Version 1903 for x64-based Systems
Windows 10 Version 1903 for ARM64-based Systems
Windows Server, Version 1903 (Server Core installation)
Windows 10 Version 1909 for 32-bit Systems
Windows 10 Version 1909 for x64-based Systems
Windows 10 Version 1909 for ARM64-based Systems
Windows Server, Version 1909 (Server Core installation)
Windows 10 Version 1903 for x64-based Systems
Windows 10 Version 1903 for ARM64-based Systems
Windows Server, Version 1903 (Server Core installation)
Windows 10 Version 1909 for 32-bit Systems
Windows 10 Version 1909 for x64-based Systems
Windows 10 Version 1909 for ARM64-based Systems
Windows Server, Version 1909 (Server Core installation)
四、漏洞原理
漏洞發生在srv2.sys中,由於SMB沒有正確處理壓縮的數據包,在解壓數據包的時候使用客戶端傳過來的長度進行解壓時,並沒有檢查長度是否合法.最終導致整數溢出。
SMB v3中支持數據壓縮,如果SMB Header中的ProtocolId為0x424D53FC也就是0xFC, 'S', 'M', 'B'.那么就說明數據是壓縮的,這時smb會調用壓縮解壓處理的函數.
首先SMB會調用srv2!Srv2ReceiveHandler函數接收smb數據包,並根據ProtocoIId設置對應的處理函數。
__int64 __fastcall Srv2ReceiveHandler(__int64 a1, void *Src, __int64 a3, unsigned int a4, unsigned int *a5, char *Srca, struct _SLIST_ENTRY *a7, _QWORD *a8) { ... // // 這里判斷頭部ProtocolId // if ( **((_DWORD **)&v20[15].Next[1].Next + 1) == 'BMS\xFC' ) { if ( KeGetCurrentIrql() > 1u ) { v20[14].Next = (_SLIST_ENTRY *)v11; v20[2].Next = (_SLIST_ENTRY *)Srv2DecompressMessageAsync; v43 = HIDWORD(v20->Next) == 5; *((_DWORD *)&v20[3].Next + 2) = 0; if ( v43 ) { LOBYTE(v71) = 1; LOBYTE(v35) = 1; SRV2_PERF_ENTER_EX(&v20[32].Next + 1, v35, 307i64, "Srv2PostToThreadPool", (_DWORD)v71); } v44 = *((_QWORD *)&v20[3].Next[8].Next + 1); v45 = *(_QWORD *)(v44 + 8i64 * KeGetCurrentNodeNumber() + 8); if ( !ExpInterlockedPushEntrySList((PSLIST_HEADER)(v45 + 16), v20 + 1) && *(_WORD *)(v45 + 66) ) RfspThreadPoolNodeWakeIdleWorker(v45); goto LABEL_168; } } }
產生整數溢出漏洞的代碼如下:
__int64 __fastcall Srv2DecompressData(__int64 pData) { __int64 v2; // rax COMPRESSION_TRANSFORM_HEADER Header; // xmm0 MAPDST __m128i v4; // xmm0 unsigned int CompressionAlgorithm; // ebp __int64 UnComparessBuffer; // rax MAPDST int v9; // eax int v11; // [rsp+60h] [rbp+8h] v11 = 0; v2 = *(_QWORD *)(pData + 0xF0); if ( *(_DWORD *)(v2 + 0x24) < 0x10u ) // 這里判斷數據包長度的最小值 return 0xC000090Bi64; Header = *(COMPRESSION_TRANSFORM_HEADER *)*(_QWORD *)(v2 + 0x18);// [v2+0x18]中為客戶端傳進來的Buffer // [v2+0x24]為數據包長度 v4 = _mm_srli_si128((__m128i)Header, 8); CompressionAlgorithm = *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(pData + 0x50) + 0x1F0i64) + 0x8Ci64); if ( CompressionAlgorithm != v4.m128i_u16[0] ) return 0xC00000BBi64; UnCompressBuffer = SrvNetAllocateBuffer((unsigned int)(Header.OriginalCompressedSegmentSize + v4.m128i_i32[1]), 0i64);// OriginalCompressedSegmentSize + CompressedSegmentSize,這里沒有檢查相加的值,導致整數溢出,分配一個較小的UnCompressBuffer if ( !UnComparessBuffer ) return 0xC000009Ai64; if ( (int)SmbCompressionDecompress( CompressionAlgorithm, // CompressionAlgorithm *(_QWORD *)(*(_QWORD *)(pData + 0xF0) + 0x18i64) + (unsigned int)Header.Length + 0x10i64,// CompressedBuffer (unsigned int)(*(_DWORD *)(*(_QWORD *)(pData + 0xF0) + 0x24i64) - Header.Length - 0x10),// CompressedBufferSize (unsigned int)Header.Length + *(_QWORD *)(UnComparessBuffer + 0x18),// UncompressedBuffer,會傳入SmbCompressionDecompress函數進行Decompress處理。 Header.OriginalCompressedSegmentSize, &v11) < 0 || (v9 = v11, v11 != Header.OriginalCompressedSegmentSize) ) { SrvNetFreeBuffer(UnComparessBuffer); return 0xC000090Bi64; } if ( Header.Length ) { memmove( *(void **)(UnComparessBuffer + 24), (const void *)(*(_QWORD *)(*(_QWORD *)(pData + 240) + 24i64) + 16i64), (unsigned int)Header.Length); v9 = v11; } *(_DWORD *)(UnComparessBuffer + 36) = Header.Length + v9; Srv2ReplaceReceiveBuffer(pData, UnComparessBuffer); return 0i64; }
五、漏洞檢測
已經很多的驗證腳本,整體的思路都是驗證回包中的特定位置是否包含十六進制的\x11\x03或\x02\x00這兩個
關鍵字。
在存在漏洞的SMB版本的通信回包如下:

python3版POC
import socket import struct import sys from netaddr import IPNetwork pkt = b'\x00\x00\x00\xc0\xfeSMB@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x08\x00\x01\x00\x00\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x00\x00\x00\x02\x00\x00\x00\x02\x02\x10\x02"\x02$\x02\x00\x03\x02\x03\x10\x03\x11\x03\x00\x00\x00\x00\x01\x00&\x00\x00\x00\x00\x00\x01\x00 \x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\n\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00' subnet = sys.argv[1] for ip in IPNetwork(subnet): sock = socket.socket(socket.AF_INET) sock.settimeout(3) try: sock.connect(( str(ip), 445 )) except: sock.close() continue sock.send(pkt) nb, = struct.unpack(">I", sock.recv(4)) res = sock.recv(nb) if res[68:70] != b"\x11\x03" or res[70:72] != b"\x02\x00": print(f"{ip} Not vulnerable.") else: print(f"{ip} Vulnerable")
也可以使用nmap的腳本進行驗證,依托nmap的強大框架,更方便。
local smb = require "smb" local stdnse = require "stdnse" local nmap = require "nmap" description = [[ smb-protocols script modified to apply check for CVE-2020-0796 by psc4re. Attempts to list the supported protocols and dialects of a SMB server. Packet check based on https://github.com/ollypwn/SMBGhost/ The script attempts to initiate a connection using the dialects: * NT LM 0.12 (SMBv1) * 2.02 (SMBv2) * 2.10 (SMBv2) * 3.00 (SMBv3) * 3.02 (SMBv3) * 3.11 (SMBv3) Additionally if SMBv1 is found enabled, it will mark it as insecure. This script is the successor to the (removed) smbv2-enabled script. ]] --- -- @usage nmap -p445 --script smb-protocols <target> -- @usage nmap -p139 --script smb-protocols <target> -- -- @output -- | smb-protocols: -- | dialects: -- | NT LM 0.12 (SMBv1) [dangerous, but default] -- | 2.02 -- | 2.10 -- | 3.00 -- | 3.02 -- |_ 3.11 (SMBv3.11) compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost -- -- @xmloutput -- <table key="dialects"> -- <elem>NT LM 0.12 (SMBv1) [dangerous, but default]</elem> -- <elem>2.02</elem> -- <elem>2.10</elem> -- <elem>3.00</elem> -- <elem>3.02</elem> -- <elem>3.11 (SMBv3.11) [Potentially Vulnerable to CVE-2020-0796 Coronablue]</elem> -- </table> --- author = "Paulino Calderon (Modified by Psc4re)" license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"safe", "discovery"} hostrule = function(host) return smb.get_port(host) ~= nil end action = function(host,port) local status, supported_dialects, overrides local output = stdnse.output_table() overrides = {} status, supported_dialects = smb.list_dialects(host, overrides) if status then for i, v in pairs(supported_dialects) do -- Mark SMBv1 as insecure if v == "NT LM 0.12" then supported_dialects[i] = v .. " (SMBv1) [dangerous, but default]" end if v == "3.11" then local msg local response local compresionalg local comp msg = '\x00\x00\x00\xc0\xfeSMB@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x08\x00\x01\x00\x00\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x00\x00\x00\x02\x00\x00\x00\x02\x02\x10\x02"\x02$\x02\x00\x03\x02\x03\x10\x03\x11\x03\x00\x00\x00\x00\x01\x00&\x00\x00\x00\x00\x00\x01\x00 \x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\n\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00' local socket = nmap.new_socket() socket:set_timeout(3000) socket:connect(host.ip,445) socket:send(msg) response,data = socket:receive() compressionalg= string.sub(data,-2) if compressionalg == "\x01\x00" then comp = "LZNT1 compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost" elseif compressionalg == "\x02\x00" then comp ="LZ77 compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost" elseif compressionalg == "\x00\x00" then comp ="No Compression Not Vulnerable" elseif compressionalg == "\x03\x00" then comp="LZ77+Huffman compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost" end supported_dialects[i] = v .." " .. comp end end output.dialects = supported_dialects end if #output.dialects>0 then return output else stdnse.debug1("No dialects were accepted") if nmap.verbosity()>1 then return "No dialects accepted. Something may be blocking the responses" end end end
六、漏洞加固
1. 更新,完成補丁的安裝。
操作步驟:設置->更新和安全->Windows更新,點擊“檢查更新”。
2.微軟給出了臨時的應對辦法:
運行regedit.exe,打開注冊表編輯器,在HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters建立一個名為DisableCompression的DWORD,值為1,禁止SMB的壓縮功能。
3.對SMB通信445端口進行封禁。
補丁鏈接
參考鏈接

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">