CVE-2023-28113 (published 2023-03-16T21:15Z)

russh is a Rust SSH client and server library. Starting in version 0.34.0 and prior to versions 0.36.2 and 0.37.1, Diffie-Hellman key validation is insufficient, which can lead to insecure shared secrets and therefore breaks confidentiality. Connections between a russh client and server or those of a russh peer with some other misbehaving peer are most likely to be problematic. These may vulnerable to eavesdropping. Most other implementations reject such keys, so this is mainly an interoperability issue in such a case. This issue is fixed in versions 0.36.2 and 0.37.1

CVSS v3 base score: 5.9, MEDIUM (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-347
cwe_name: Improper Verification of Cryptographic Signature
description: The product does not verify, or incorrectly verifies, the cryptographic signature for data.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/warp-tech/russh
commit_details: repo_url: https://github.com/warp-tech/russh
author: Eugene Pankov
author_date: 2023-03-15 20:56:09+01:00
msg: GHSA-cqvm-j2r2-hwpg validate DH key range
repository: repo_url: https://github.com/warp-tech/russh
repo_name: Eugeny/russh
description: Rust SSH client & server library
repo_language: Rust
owner: warp-tech
file_changes: filename: groups.rs
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,3 @@
-use std::ops::Shl;
-
 use hex_literal::hex;
 use num_bigint::{BigUint, RandBigInt};
 use rand;
@@ -69,9 +67,17 @@ impl DH {
         }
     }
 
-    pub fn generate_private_key(&mut self) -> BigUint {
+    pub fn generate_private_key(&mut self, is_server: bool) -> BigUint {
+        let q = (&self.prime_num - &BigUint::from(1u8)) / &BigUint::from(2u8);
         let mut rng = rand::thread_rng();
-        self.private_key = rng.gen_biguint((self.exp_size * 8) - 2u64).shl(1);
+        self.private_key = rng.gen_biguint_range(
+            &if is_server {
+                1u8.into()
+            } else {
+                2u8.into()
+            },
+            &q,
+        );
         self.private_key.clone()
     }
 
@@ -85,7 +91,21 @@ impl DH {
         self.shared_secret.clone()
     }
 
+    pub fn validate_shared_secret(&self, shared_secret: &BigUint) -> bool {
+        let one = BigUint::from(1u8);
+        let prime_minus_one = &self.prime_num - &one;
+
+        shared_secret > &one && shared_secret < &prime_minus_one
+    }
+
     pub fn decode_public_key(buffer: &[u8]) -> BigUint {
         BigUint::from_bytes_be(buffer)
     }
+
+    pub fn validate_public_key(&self, public_key: &BigUint) -> bool {
+        let one = BigUint::from(1u8);
+        let prime_minus_one = &self.prime_num - &one;
+
+        public_key > &one && public_key < &prime_minus_one
+    }
 }

programming_language: Rust
filename: mod.rs
change_type: ModificationType.MODIFY
diff: @@ -4,6 +4,7 @@ use std::marker::PhantomData;
 use byteorder::{BigEndian, ByteOrder};
 use digest::Digest;
 use groups::DH;
+use log::debug;
 use num_bigint::BigUint;
 use russh_cryptovec::CryptoVec;
 use russh_keys::encoding::Encoding;
@@ -104,16 +105,27 @@ impl<D: Digest> KexAlgorithm for DhGroupKex<D> {
 
         debug!("client_pubkey: {:?}", client_pubkey);
 
-        self.dh.generate_private_key();
-        let server_pubkey = biguint_to_mpint(&self.dh.generate_public_key());
+        self.dh.generate_private_key(true);
+        let server_pubkey = &self.dh.generate_public_key();
+        if !self.dh.validate_public_key(server_pubkey) {
+            return Err(crate::Error::Inconsistent);
+        }
+
+        let encoded_server_pubkey = biguint_to_mpint(server_pubkey);
 
         // fill exchange.
         exchange.server_ephemeral.clear();
-        exchange.server_ephemeral.extend(&server_pubkey);
+        exchange.server_ephemeral.extend(&encoded_server_pubkey);
+
+        let decoded_client_pubkey = DH::decode_public_key(client_pubkey);
+        if !self.dh.validate_public_key(&decoded_client_pubkey) {
+            return Err(crate::Error::Inconsistent);
+        }
 
-        let shared = self
-            .dh
-            .compute_shared_secret(DH::decode_public_key(client_pubkey));
+        let shared = self.dh.compute_shared_secret(decoded_client_pubkey);
+        if !self.dh.validate_shared_secret(&shared) {
+            return Err(crate::Error::Inconsistent);
+        }
         self.shared_secret = Some(biguint_to_mpint(&shared));
         Ok(())
     }
@@ -124,22 +136,35 @@ impl<D: Digest> KexAlgorithm for DhGroupKex<D> {
         client_ephemeral: &mut CryptoVec,
         buf: &mut CryptoVec,
     ) -> Result<(), crate::Error> {
-        self.dh.generate_private_key();
-        let client_pubkey = biguint_to_mpint(&self.dh.generate_public_key());
+        self.dh.generate_private_key(false);
+        let client_pubkey = &self.dh.generate_public_key();
+
+        if !self.dh.validate_public_key(client_pubkey) {
+            return Err(crate::Error::Inconsistent);
+        }
 
         // fill exchange.
+        let encoded_pubkey = biguint_to_mpint(client_pubkey);
         client_ephemeral.clear();
-        client_ephemeral.extend(&client_pubkey);
+        client_ephemeral.extend(&encoded_pubkey);
 
         buf.push(msg::KEX_ECDH_INIT);
-        buf.extend_ssh_string(&client_pubkey);
+        buf.extend_ssh_string(&encoded_pubkey);
 
         Ok(())
     }
 
     fn compute_shared_secret(&mut self, remote_pubkey_: &[u8]) -> Result<(), crate::Error> {
         let remote_pubkey = DH::decode_public_key(remote_pubkey_);
+
+        if !self.dh.validate_public_key(&remote_pubkey) {
+            return Err(crate::Error::Inconsistent);
+        }
+
         let shared = self.dh.compute_shared_secret(remote_pubkey);
+        if !self.dh.validate_shared_secret(&shared) {
+            return Err(crate::Error::Inconsistent);
+        }
         self.shared_secret = Some(biguint_to_mpint(&shared));
         Ok(())
     }

programming_language: Rust