CVE-2023-43660 (Rust, published 2023-09-27T22:15Z)
Description: value: Warpgate is a smart SSH, HTTPS and MySQL bastion host for Linux that doesn't need special client apps. The SSH key verification for a user can be bypassed by sending an SSH key offer without a signature. This allows bypassing authentication under following conditions: 1. The attacker knows the username and a valid target name 2. The attacked knows the user's public key and 3. Only SSH public key authentication is required for the user account. This issue has been addressed in version 0.8.1. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Severity: nan (CVSS v3 base score: 8.1)
Weakness: CWE-347 \u2014 Improper Verification of Cryptographic Signature
The product does not verify, or incorrectly verifies, the cryptographic signature for data.
Repository: https://github.com/warp-tech/warpgate
Files changed:
Cargo.lock
warpgate-protocol-ssh/Cargo.toml
warpgate-protocol-ssh/src/server/russh_handler.rs
warpgate-protocol-ssh/src/server/session.rs
Commit message: fixed GHSA-3cjp-w4cp-m9c8 - interpreting SSH public key offers as a successful authentication


Vulnerable code:
version = "1.1.0"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
version = "0.3.0"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
version = "2.2.0"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
version = "0.38.0"
checksum = "ae0efcc0f4cd6c062c07e572ce4b806e3967fa029fcbfcc0aa98fb5910a37925"
version = "2.3.1"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
version = "0.7.4"
russh = { version = "0.38.0", features = ["vendored-openssl"] }
    async fn _auth_publickey(
        &mut self,
        ssh_username: Secret<String>,
        key: PublicKey,
    ) -> russh::server::Auth {
        let selector: AuthSelector = ssh_username.expose_secret().into();

        info!(
            "Public key auth as {:?} with key {}",
            selector,
            key.public_key_base64()
        );


Fixed code:
version = "1.2.0"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
version = "0.4.0"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
version = "2.3.0"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
version = "0.39.0"
checksum = "7878311587d0353a854d5be954fbe68bdf6e77873933b484d1e45db12bb2f8cf"
version = "2.4.1"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
version = "0.8.0"
version = "0.8.0"
version = "0.8.0"
version = "0.8.0"
version = "0.8.0"
version = "0.8.0"
version = "0.8.0"
version = "0.8.0"
 "url",
version = "0.8.0"
version = "0.8.0"
version = "0.8.0"
version = "0.8.0"
russh = { version = "0.39.0", features = ["vendored-openssl"] }
    AuthPublicKeyOffer(Secret<String>, PublicKey, oneshot::Sender<bool>),
    async fn auth_publickey_offered(
        self,
        user: &str,
        key: &russh_keys::key::PublicKey,
    ) -> Result<(Self, Auth), Self::Error> {
        let user = Secret::new(user.to_string());
        let (tx, rx) = oneshot::channel();

        self.send_event(ServerHandlerEvent::AuthPublicKeyOffer(
            user,
            key.clone(),
            tx,
        ))?;

        let result = rx.await.unwrap_or(false);
        Ok((
            self,
            if result {
                Auth::Accept
            } else {
                Auth::Reject {
                    proceed_with_methods: None,
                }
            },
        ))
    }

            ServerHandlerEvent::AuthPublicKeyOffer(username, key, reply) => {
                let _ = reply.send(self._auth_publickey_offer(username, key).await);
            }

    fn _get_public_keys_from_of(&self, key: PublicKey) -> Vec<PublicKey> {
        keys
    }

    async fn _auth_publickey_offer(
        &mut self,
        ssh_username: Secret<String>,
        key: PublicKey,
    ) -> bool {
        let keys = self._get_public_keys_from_of(key);
        let selector: AuthSelector = ssh_username.expose_secret().into();

        for key in keys {
            if let Ok(true) = self
                .try_validate_public_key_offer(
                    &selector,
                    Some(AuthCredential::PublicKey {
                        kind: key.name().to_string(),
                        public_key_bytes: Bytes::from(key.public_key_bytes()),
                    }),
                )
                .await
            {
                return true;
            }
        }
        false
    }

    async fn _auth_publickey(
        &mut self,
        ssh_username: Secret<String>,
        key: PublicKey,
    ) -> russh::server::Auth {
        let selector: AuthSelector = ssh_username.expose_secret().into();

        info!(
            "Public key auth as {:?} with key {}",
            selector,
            key.public_key_base64()
        );

        let keys = self._get_public_keys_from_of(key);
    async fn try_validate_public_key_offer(
        &mut self,
        selector: &AuthSelector,
        credential: Option<AuthCredential>,
    ) -> Result<bool> {
        match selector {
            AuthSelector::User { username, .. } => {
                let cp = self.services.config_provider.clone();

                if let Some(credential) = credential {
                    return Ok(cp
                        .lock()
                        .await
                        .validate_credential(username, &credential)
                        .await?);
                }

                Ok(false)
            }
            _ => Ok(false),
        }
    }