Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 3 min read

CVE-2026-128087: CVE-2026-128087: Premature Key Exchange Callback Invocation Prior to Host Key Verification in russh

CVE-2026-128087: Premature Key Exchange Callback Invocation Prior to Host Key Verification in russh Vulnerability ID: CVE-2026-128087 CVSS Score: 3.7 Published: 2026-11-13 CVE-2026-128087 (GHSA-9264-jvgf-6w2f) is an

CVE-2026-128087: Premature Key Exchange Callback Invocation Prior to Host Key Verification in russh

Vulnerability ID: CVE-2026-128087
CVSS Score: 3.7
Published: 2026-11-13

CVE-2026-128087 (GHSA-9264-jvgf-6w2f) is an incorrect behavior order (CWE-696) and improper authorization (CWE-863) vulnerability in the russh Rust SSH library, affecting versions 0.55.0 through 0.65.0. During the initial SSH key exchange (KEX) handshake, the client state machine invokes the client::Handler::kex_done() callbackβ€”providing the negotiated raw shared secret bytesβ€”prior to validating the remote server's host key or certificate via check_server_key(). Consequently, an untrusted or rogue SSH server can complete cryptographic key agreement and force a connecting client to execute side-effecting application logic inside kex_done() before the server's identity is checked and rejected.

TL;DR

In russh versions 0.55.0 through 0.65.0, the client::Handler::kex_done() callback is invoked with the raw SSH shared secret before check_server_key() verifies the remote server's identity. Applications using kex_done() to initialize secondary channels or process shared secrets can be forced to interact with an unauthenticated server. Fixed in version 0.65.1.

Technical Details

  • CVE / Advisory ID: CVE-2026-128087 / GHSA-9264-jvgf-6w2f
  • Weakness Class: CWE-696 (Incorrect Behavior Order) / CWE-863 (Incorrect Authorization)
  • Affected Component: russh crate (russh::client::Handler::kex_done)
  • CVSS v3.1 Score: 3.7 Low (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N)
  • EPSS Score: 0.24% (15.05th Percentile)
  • Impact: Premature shared secret callback execution against unauthenticated SSH servers
  • Exploit & KEV Status: No known public exploits; Not listed in CISA KEV
  • Patched Version: russh >= 0.65.1

Affected Systems

  • russh (Rust crate versions >= 0.55.0, < 0.65.1)
  • russh: >= 0.55.0, < 0.65.1 (Fixed in: 0.65.1)

Code Analysis

Commit: 43a46d6

Ensure client::Handler::kex_done() runs only after check_server_key() during the initial SSH key exchange.

File: russh/src/client/mod.rs
@@ -2033,14 +2033,11 @@ async fn reply<H: Handler>(
                     session.common.strict_kex =
                         session.common.strict_kex || newkeys.names.strict_kex();

-                    // Call the kex_done handler before consuming newkeys
-                    let shared_secret = newkeys.kex.shared_secret_bytes();
-                    handler
-                        .kex_done(shared_secret, &newkeys.names, session)
-                        .await?;
-
                     if session.common.encrypted.is_some() {
                         // This is a rekey
+                        handler
+                            .kex_done(newkeys.kex.shared_secret_bytes(), &newkeys.names, session)
+                            .await?;
                         {
                             let common = &mut session.common;
                             common.newkeys(newkeys);
@@ -2077,6 +2074,10 @@ async fn reply<H: Handler>(
                             }
                         }

+                        handler
+                            .kex_done(newkeys.kex.shared_secret_bytes(), &newkeys.names, session)
+                            .await?;
+
                         session
                             .common
                             .encrypted(initial_encrypted_state(session), newkeys);
@@ -2812,6 +2813,11 @@ pub trait Handler: Sized + Send {

     /// Called when key exchange has completed.
     ///
+    /// For the initial key exchange this runs only after
+    /// [`check_server_key`](Self::check_server_key) has accepted the server,
+    /// so the shared secret never reaches the application for a server that
+    /// is about to be rejected.
+    ///
     /// This callback provides access to the raw shared secret from the KEX,
     /// which is useful for protocols that derive additional keys from the
     /// SSH shared secret (e.g., for secondary encrypted channels).

Mitigation Strategies

  • Upgrade the russh crate dependency to version 0.65.1 or later to enforce host key verification prior to kex_done() invocation.
  • Defer side-effecting operations, secondary channel initialization, and out-of-band transmissions using kex_done() key material until after check_server_key() or client authentication succeeds.
  • Ensure custom implementations of check_server_key() perform synchronous or properly awaited validation before returning Ok(true).

Remediation Steps:

  1. Update Cargo.toml to specify russh = ">= 0.65.1".
  2. Execute 'cargo update -p russh' to refresh the Cargo.lock file to the patched release.
  3. Review custom implementations of the russh::client::Handler trait in the codebase to audit how kex_done() and check_server_key() interact.
  4. Recompile and redeploy all affected Rust binaries and services.

References

Read the full report for CVE-2026-128087 on our website for more details including interactive diagrams and full exploit analysis.

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.