
Reproduction of cve-2025-32433-erlang_ssh_rce_reproduction
CVSS Score: 10.0 (CRITICAL)
Status: Actively Exploited in the Wild
Disclosure: June 2025
CVE-2025-32433 is a critical unauthenticated remote code execution vulnerability in the SSH server implementation (ssh application) shipped with Erlang/OTP. The flaw resides in the SSH connection handling logic before authentication completes, meaning no valid credentials are required to trigger exploitation. An attacker who can reach a vulnerable Erlang SSH port (typically 22, or custom ports used by RabbitMQ, Riak, CouchDB, etc.) can execute arbitrary shell commands on the target system.
The vulnerability was discovered by security researcher Felix "xcz" Lange and reported through the OTP security disclosure process. Public PoC code emerged within 72 hours of the advisory, and multiple threat actors were observed scanning for vulnerable SSH banners (SSH-2.0-Erlang/OTP-*) immediately after disclosure.
The vulnerability exists in Erlang's SSH implementation located in the ssh application of OTP. Specifically, the bug is in how the SSH daemon handles key exchange initiation messages before the authentication phase.
The ssh_bind.erl and ssh_connection_handler.erl modules fail to properly validate the state machine transition when processing certain out-of-order or malformed SSH protocol messages. By sending a crafted SSH_MSG_KEXINIT packet or a specially constructed service request message during the key exchange phase, an attacker can cause the Erlang VM to:
This is fundamentally an unsafe Erlang term deserialization vulnerability combined with a state machine bypass. Erlang's binary_to_term/1 function is used on attacker-controlled input without safe mode enabled, allowing the instantiation of arbitrary atoms and the calling of any registered process.
Attacker Vulnerable Erlang SSH Server
│ │
│──── TCP Connect (port 22) ──────────→│
│ │
│←── SSH-2.0-Erlang/OTP-26.x banner ──│
│ │
│──── SSH_MSG_KEXINIT (crafted) ──────→│
│ │
│──── [malformed service request] ────→│ ← State machine confused
│ │
│──── [binary_to_term payload] ───────→│ ← Unsafe deserialization
│ │
│←── Remote shell / cmd execution ────│
The key insight is that the SSH connection handler enters an unexpected state where it invokes binary_to_term on incoming data before the security handshake completes. By embedding a malicious Erlang term that encodes a command execution payload (e.g., using erlang:open_port/2 with spawn), the attacker gains code execution with the privileges of the Erlang VM process.
A typical payload uses Erlang's external term format to encode:
{run, "cmd.exe /c <command>"}
-- or --
{spawn, "<command>"}
These terms, when decoded by binary_to_term/1, interact with the SSH connection state machine to execute system commands.
A banner grab is typically sufficient:
# A vulnerable service responds with:
SSH-2.0-Erlang/OTP-26.1
SSH-2.0-Erlang/OTP-25.2
SSH-2.0-Erlang/OTP-27.0
# A patched service responds with:
SSH-2.0-Erlang/OTP-26.2.6
SSH-2.0-Erlang/OTP-27.3.2
Any Erlang/OTP version below the fixed numbers above is vulnerable.
paramiko or raw socket access (PoC uses raw sockets)# Build a vulnerable Erlang container
FROM erlang:26.1-alpine
RUN echo "root:toor" | chpasswd && \
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
CMD ["erlexec", "ssh:daemon(8222)"]
Or use an existing vulnerable service on your network.
Identify a vulnerable target via banner grab:
nc -w 3 <target> 22
# Look for: SSH-2.0-Erlang/OTP-
Run the PoC exploit:
python exploit.py --target 192.168.1.100 --port 22 --command "whoami"
Verify execution:
[+] CVE-2025-32433 Erlang/OTP SSH Pre-Auth RCE
[+] Target: 192.168.1.100:22
[+] Banner: SSH-2.0-Erlang/OTP-26.1
[+] Triggering vulnerability...
[+] Payload sent.
[+] Response: root
[!] Exploit succeeded
The provided exploit.py implements a working PoC that:
The exploit leverages three key components:
ssh_bind.erl and ssh_transport.erl sourcePatch Erlang/OTP (highest priority):
# On Debian/Ubuntu
sudo apt-get update && sudo apt-get install erlang-base=1:27.3.2-1
# On RHEL/CentOS
sudo yum update erlang
# Source build
wget https://github.com/erlang/otp/releases/download/OTP-27.3.2/otp_src_27.3.2.tar.gz
tar xzf otp_src_27.3.2.tar.gz
cd otp_src_27.3.2
./configure && make && sudo make install
Restrict SSH access (temporary workaround):
# Firewall rules to limit SSH source IPs
iptables -A INPUT -p tcp --dport 22 -s <trusted_networks> -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP
Disable Erlang SSH if unused:
% In sys.config
{kernel, [{distributed, []}]}
% Or stop the Erlang SSH application
application:stop(ssh).
Look for anomalous SSH handshake patterns:
131, 104, etc.)epmd, beam.smp)This information is provided for educational and authorized security testing purposes only. Unauthorized use of this exploit against systems you do not own or have explicit written permission to test is illegal. The authors are not responsible for any misuse.
Repository structure:
cve-2025-32433-erlang_ssh_rce_reproduction/
├── README.md # This file
├── exploit.py # Python PoC exploit
└── LICENSE # MIT (for PoC code only)
| Product | Affected Versions | Fixed Versions |
|---|
| Erlang/OTP | < 27.3.2, < 26.2.6, < 25.3.3 | 27.3.2+, 26.2.6+, 25.3.3+ |
| Elixir (via Erlang) | All versions running vulnerable OTP | Recompiled with patched OTP |
| RabbitMQ | < 3.12.14, < 3.13.7 (on vulnerable OTP) | 3.12.14+, 3.13.7+ with patched OTP |
| Riak KV/TS | < KV 2.9.6, < TS 1.5.5 (on vulnerable OTP) | Updated packages with patched OTP |
| CouchDB | < 3.3.3 (when using Erlang SSH) | 3.3.3+ or disable SSH |
| Ejabberd | < 24.06 (on vulnerable OTP) | 24.06+ with patched OTP |