zoukankan      html  css  js  c++  java
  • SMB远程代码执行漏洞(CVE-2020-0796)分析、验证及加固

            这几天有点忙,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)

    四、漏洞原理

        漏洞发生在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) == 'BMSxFC' )
          {
            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;
    }

    五、漏洞检测

           已经很多的验证脚本,整体的思路都是验证回包中的特定位置是否包含十六进制的x11x03或x02x00这两个关键字
    在存在漏洞的SMB版本的通信回包如下:
    python3版POC
    import socket
    import struct
    import sys
    from netaddr import IPNetwork
    
    pkt = b'x00x00x00xc0xfeSMB@x00x00x00x00x00x00x00x00x00x1fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00$x00x08x00x01x00x00x00x7fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xx00x00x00x02x00x00x00x02x02x10x02"x02$x02x00x03x02x03x10x03x11x03x00x00x00x00x01x00&x00x00x00x00x00x01x00 x00x01x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x03x00
    x00x00x00x00x00x01x00x00x00x01x00x00x00x01x00x00x00x00x00x00x00'
    
    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"x11x03" or res[70:72] != b"x02x00":
            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 = 'x00x00x00xc0xfeSMB@x00x00x00x00x00x00x00x00x00x1fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00$x00x08x00x01x00x00x00x7fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xx00x00x00x02x00x00x00x02x02x10x02"x02$x02x00x03x02x03x10x03x11x03x00x00x00x00x01x00&x00x00x00x00x00x01x00 x00x01x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x03x00
    x00x00x00x00x00x01x00x00x00x01x00x00x00x01x00x00x00x00x00x00x00'
            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 == "x01x00" then
              comp = "LZNT1 compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost"
            elseif compressionalg == "x02x00" then
              comp ="LZ77 compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost"
            elseif compressionalg == "x00x00" then
              comp ="No Compression Not Vulnerable"
            elseif compressionalg == "x03x00" 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,打开注册表编辑器,在HKLMSYSTEMCurrentControlSetServicesLanmanServerParameters建立一个名为DisableCompression的DWORD,值为1,禁止SMB的压缩功能。

    3.对SMB通信445端口进行封禁。

     
     
     
     

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

  • 相关阅读:
    服务器监控
    Ubuntu16.04安装印象笔记
    在vi中打开多个文件,复制一个文件中多行到另一个文件中
    Ubuntu16.04安装和卸载MySQL 5.7
    Ubuntu16.04 sever 安装
    查看ubuntu 各系统的内核版本
    Ubuntu16.04中查看硬盘的型号和读取速度
    python 实现3-2 问候语: 继续使用练习 3-1 中的列表,但不打印每个朋友的姓名,而为每人打印一条消息。每条消息都包含相同的问候语,但抬头为相应朋友的姓名。
    线程
    并发编程
  • 原文地址:https://www.cnblogs.com/A66666/p/29635a243378b49ccb485c7a280df989.html
Copyright © 2011-2022 走看看