Most critical vulnerabilities are a bug in how something is parsed or how a buffer is sized. This one is different, and that is what makes it worth studying. It is a bug in when the server is willing to act. SSH is not a single conversation, it is three protocols stacked on top of each other, and each layer is only supposed to unlock after the one below it has done its job. CVE-2025-32433 is what happens when the top layer stops checking whether the middle layer ever finished, so a stranger who has done nothing but negotiate encryption keys can tell the server to run a command, and it does. Verifiable security.
The bug in one paragraph
The Erlang/OTP SSH daemon, the ssh application shipped with the Erlang runtime, drives each connection through a state machine. After the transport handshake, the connection is supposed to accept only user-authentication traffic from the peer, and to stay in that state until authentication succeeds. Only then may it advance to a state where connection-protocol messages, the ones that open channels and run programs, are valid. In the affected versions the dispatcher did not enforce that ordering. When a peer sent a connection-protocol message such as SSH_MSG_CHANNEL_OPEN or SSH_MSG_CHANNEL_REQUEST, message numbers in the 90-and-above range, the server processed it even though authentication had never completed. The gate that should have rejected those messages until after login was simply not there. This is a textbook CWE-306, Missing Authentication for a Critical Function: the critical function is command execution, and the authentication meant to guard it was missing from the path that reaches it.
How SSH is supposed to gate this
To see why the missing check is so severe, it helps to walk the handshake as the protocol defines it. An SSH connection is built in three phases, meant to be strictly ordered.
The first phase is the transport layer. The client connects, the two sides exchange version strings, and they run a key exchange that produces the session keys and verifies the server's host key. At the end of it the channel is encrypted and integrity-protected, but the server knows nothing about who the client is. Encryption is not identity. All the transport layer proves is that nobody in the middle can read the traffic, not that the peer is allowed to be there.
The second phase is the user-authentication protocol. This is where the client proves who it is, with a password, a public key, or another method the server accepts. Until it completes successfully, the client is anonymous, and the protocol is explicit that the connection must not offer any real service during this window. The only legitimate traffic is the back-and-forth of authentication itself.
The third phase is the connection protocol. This is the part of SSH that does useful work: it multiplexes the encrypted tunnel into channels, which carry an interactive shell, an exec of a single command, port forwarding, or a file-transfer subsystem. The messages that drive it, above all SSH_MSG_CHANNEL_OPEN and SSH_MSG_CHANNEL_REQUEST, are only meaningful once the client has authenticated. The whole security model rests on the second phase standing as a wall between the first and the third.
The intended order is transport, then authentication, then connection. CVE-2025-32433 lets connection-protocol messages be honored straight out of phase one. Illustrative, not from any specific host.
Where the gate went missing
Put those pieces together and the flaw is easy to state. The affected server let a peer move from phase one to phase three without passing through phase two. An attacker opens a TCP connection to the SSH port and completes only the transport-layer handshake, never sending credentials because it never tries to authenticate. Then, from that unauthenticated state, it sends SSH_MSG_CHANNEL_OPEN for a channel of type session, followed by a SSH_MSG_CHANNEL_REQUEST such as exec carrying a command. Because the dispatcher did not condition channel handling on a completed authentication state, it opened the session channel and ran it.
The reason a state machine makes this kind of bug possible is that the rule being broken is not carried in the shape of any one message. A CHANNEL_OPEN is a legal message, and an exec request is a legal request. There is nothing malformed to reject and no buffer to overrun. When the transition that should fire only after authentication succeeds instead fires whenever a connection-protocol message arrives, each message looks fine while the sequence violates the protocol. That is why review and testing can miss it: the parts are all valid, and only the composition is wrong.
Every message the attacker sends is well-formed. Nothing is malformed and nothing overflows. The only thing wrong is the order, and order is precisely the property the state machine was supposed to enforce.
Why it reaches root
A command-execution bug is bad in proportion to the privilege of the process that runs the command, and this is where CVE-2025-32433 earns its perfect score. In many Erlang/OTP deployments the SSH service is not a general-purpose login shell for people. It is an embedded management interface baked into a larger product, and it commonly runs with high privilege, frequently as root or an equally powerful service account. When the vulnerable dispatcher runs the attacker's command, it uses that privilege, so this is not merely unauthenticated code execution but unauthenticated code execution at the service's privilege level, which on these systems is often the highest available. A minimal proof of concept need not be clever: having the server write a file or invoke an OS command through Erlang's os:cmd is enough to show arbitrary code running at that privilege.
The exposure is wide because Erlang/OTP is not a niche runtime. It sits underneath telecom infrastructure, message brokers, databases, IoT and operational-technology devices, and a range of backend platforms, many of which embed the OTP SSH server as a remote-management channel. So the affected population mixes obvious servers with things that do not look like servers at all: appliances whose SSH interface is a convenience feature nobody thinks of as an attack surface. The common thread is that the port is network-reachable, needs no credentials, and needs no user interaction. That combination, spelled out in the vector AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H, produces the 10.0.
Which versions, and the fixed builds
The vulnerable code is in the ssh application across a broad span of Erlang/OTP releases. The affected ranges are OTP releases prior to 25.3.2.20, the OTP 26 line from 26.0 through 26.2.5.10, and the OTP 27 line from 27.0 through 27.3.2. The fixes landed in OTP-25.3.2.20, OTP-26.2.5.11, and OTP-27.3.3. Because Erlang/OTP is so often embedded, the version that matters is not always the one an operator installed directly. It is the OTP version bundled inside a product, so the same fix reaches different environments on different vendor timelines, and a device can stay vulnerable long after the upstream patch exists simply because the product embedding it has not shipped an update.
CISA added CVE-2025-32433 to the Known Exploited Vulnerabilities catalog on 9 June 2025, with a remediation due date of 30 June 2025. A catalog listing is a statement of fact, not a forecast: it records exploitation observed in the wild. For any internet-reachable instance on an affected build, that reframes the work from patch-when-convenient to treat-as-active.
How to recognize your exposure
You can assess this risk without ever sending a malicious sequence, because the exposure is fixed by three readable facts, none needing you to drive the connection protocol against a live host.
- Is an Erlang/OTP SSH service reachable, and from where? The relevant surface is any listener served by the OTP
sshapplication, whether an obvious management port on a backend platform or a quiet SSH interface on an appliance. Knowing which answer from outside your perimeter tells you where the bug would land. - What OTP version backs that service, and is it in an affected range? Version is the highest-signal fact here, because the affected and fixed builds are named precisely. An OTP version below 25.3.2.20, or in 26.0 through 26.2.5.10, or in 27.0 through 27.3.2, is exposed. The catalog listing means any reachable one is an active risk.
- What privilege does the service run with? The blast radius is the privilege of the process that executes the command. A service running as root turns a single unauthenticated sequence into root-level execution, so the run-as account tells you how bad a hit is before one happens.
Find and close the Erlang/OTP SSH pre-auth path
- Patch to a fixed build now. Upgrade the embedded or installed runtime to OTP-27.3.3, OTP-26.2.5.11, or OTP-25.3.2.20 for its line. For products that bundle OTP, track the vendor advisory and apply the update that carries the fixed runtime.
- If you cannot patch immediately, cut off reach. Disable the Erlang SSH server where it is not needed, or restrict the port with a firewall allow-list so only known management hosts can connect. Removing network reachability removes the precondition for the attack.
- Inventory where OTP is embedded, not just where you installed it. The vulnerable version can be inside an appliance or device whose SSH interface you never think of as Erlang. Map the actual runtime version behind each SSH listener.
- Reduce the run-as privilege of the SSH service. Where the product allows it, run the service as a least-privilege account. It does not close the bug, but it shrinks the worst case from root to something contained.
- Watch for channel messages that arrive before an auth success. The clean detection signal is any SSH session where channel-open or channel-request messages appear before authentication completes, plus any unexpected command execution from the SSH service process.
- Treat catalog-listed, internet-facing instances as incidents. A reachable service on an affected build is on the known-exploited catalog for a reason, so give it an incident SLA, not a routine patch window.
The wider lesson: state, not syntax
It is tempting to file CVE-2025-32433 as one more critical bug and move on to the patch. The more useful reading is what it says about authentication flaws in general. The dangerous mistakes are usually not in the checks themselves but in the ordering that decides when the checks run. Here the password and public-key checks were presumably fine. What failed was the guarantee that command handling only becomes reachable after one of them passes. Any protocol that separates "prove who you are" from "now do work" depends on a state machine to keep the second locked behind the first, and every such machine has transitions that must never fire early. That is why this class is worth hunting deliberately rather than hoping a generic scanner trips over it.
Be precise about the scope, because precision is the point. Not every Erlang/OTP deployment exposes its SSH server to the internet, not every build is in the affected range, and a patched or firewalled instance carries a small fraction of the risk. The work is not to assume every OTP host is compromised. It is to determine which SSH surfaces are backed by the OTP ssh application, on which versions, at what privilege, and to close the reachable, unpatched ones. And to be clear about what this analysis is: the vulnerability is the vendor's, and patching the runtime is the fix. What we add is the exposure determination, which reachable services carry the catalog's risk right now.
How Celvex catches this
Find. Prove. Fix. Verify.
A read-only sweep maps your internet-facing SSH listeners, identifies those backed by the Erlang/OTP ssh application, fingerprints the OTP version, and cross-references it against the affected ranges and the known-exploited catalog, without ever driving the connection protocol.
A confirmed exposed, affected-version service becomes an Ed25519-signed Proof Capsule carrying the host, the version evidence, and the matching CVE-2025-32433 catalog entry, reproducible offline by you or your auditor.
The capsule's remediation block names the steps: upgrade to the fixed OTP build for that line, restrict the port to known management hosts, and reduce the service privilege where the product allows it.
A fresh sweep confirms the runtime is on a fixed build or the surface is no longer reachable. The finding closes and the verified-fix event is recorded for the audit trail.
The reason a bug like this survives is not that anyone forgot how SSH works. It is that an embedded management interface gets filed under trusted infrastructure, and the ordering question that would be obvious on a public login form goes unasked for the appliance in the rack. The discipline is to ask it everywhere authentication is supposed to gate work: which of my services will act for a peer that has not proven who it is. Answer that for the OTP SSH server on your perimeter, and CVE-2025-32433 stops being a surprise.
Verifiable security. Find it. Prove it. Fix it. Verify the fix held. That is what we ship.
Sources
Which of your SSH surfaces run on an affected Erlang/OTP build?
Free Exposure Check, no signup required. We map your internet-facing SSH listeners, identify the ones backed by the Erlang/OTP server, fingerprint the OTP version against the affected ranges and the known-exploited catalog, and ship a signed Proof Capsule for the highest-confidence finding.
Run a Free Scan →