
Cloud Vulnerability DB
A community-led vulnerabilities database
Fixed in net-ssh 8.1.1 and 7.3.9. Host keys on a @revoked line in known_hosts are now refused by every verifier, matching OpenSSH.
Upgrade. On older versions, remove the revoked key from every known_hosts file net-ssh reads, including the @revoked line itself, and use verify_host_key: :always so an unknown key is refused rather than added.
Net::SSH's known_hosts parser (Net::SSH::KnownHosts#keys_for) reads the OpenSSH @marker field but only special-cases @cert-authority. A line marked @revoked, which is the standard OpenSSH way to permanently retire a compromised host key, falls into the default branch and is loaded as a regular, trusted HostKeyEntries::PubKey pin. Every built-in host-key verifier (:always, :accept_new, and the default :accept_new_or_local_tunnel) then matches a server that presents exactly that revoked key and accepts it, even under the strictest verifier. A revoked key is one the operator already knows is compromised, and here it authenticates the host anyway. The revocation entry quietly turns into an authorization entry. OpenSSH's hostfile.c/check_host_key returns HOST_REVOKED for the same line and ssh(1) aborts with "Host key ... was revoked", and ssh_config(5) says keys on a @revoked line "must always be refused".
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N. AC:H accounts for the attacker needing the revoked private key; since a key is revoked because it is compromised, AC:L (9.1) is arguable.This is a different bug from the repo's sibling advisory GHSA-vxj5-fg65-x765 (published 2026-10-04). That one covers the @cert-authority marker and a different sink, CertAuthority#matches_key? skipping principal and validity checks, and it was patched in 7.3.4 and 8.0.0.beta3. That fix did not touch @revoked. Before the fix, net-ssh had no handling of @revoked at all, so OpenSSH's "distrust this key" is silently inverted into "trust this key".
Reachability is niche, and I want to be plain about it. A manual @revoked <host> <type> <blob> line has to already exist in a known_hosts file that net-ssh reads, and hand-written @revoked lines are an obscure OpenSSH mechanism that few operators use. Where such a line does exist, the holder of the revoked private key gets a reliable accept instead of the hard refusal OpenSSH would give.
An attacker who possesses a revoked host private key and has a network position (MITM, DNS spoof, or control of the original compromised host) presents K_S during key exchange:
lib/net/ssh/transport/session.rb:95-99 — transport connects; Algorithms#start runs KEX.lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb:103 (analogously
kex/abstract5656.rb:53-54, kex/curve25519_sha256.rb, kex/diffie_hellman_group_exchange_sha1.rb) —
source: server-controlled KEXDH_REPLY / KEXECDH_REPLY is parsed;
result[:server_key] = Net::SSH::Buffer.new(result[:key_blob]).read_key → attacker's revoked key object.lib/net/ssh/transport/kex/abstract.rb:48-49 — exchange_keys → verify_server_key(result[:server_key]).lib/net/ssh/transport/kex/abstract.rb:82 — sink entry: connection.host_key_verifier.verify(key: key, key_blob: blob, fingerprint: fingerprint, session: connection).lib/net/ssh/transport/session.rb:359-368 — select_host_key_verifier: :always → Verifiers::Always, nil (default) → AcceptNewOrLocalTunnel < AcceptNew < Always — all inherit the vulnerable Always#verify.lib/net/ssh/verifiers/always.rb:16 — host_keys = arguments[:session].host_keys →
lib/net/ssh/transport/session.rb:108-113 → KnownHosts.search_for →
lib/net/ssh/known_hosts.rb:163-171 → KnownHosts#keys_for.lib/net/ssh/known_hosts.rb:242-243 — line @revoked <host> <type> <keyblob> starts with @, so it is
parsed with the marker: marker, hosts, type, key_content, comment = line.split(' '); marker == "@revoked".lib/net/ssh/known_hosts.rb:254-256 — type is supported (e.g. ssh-rsa, ssh-ed25519) and the host
pattern matches (line 277-290 match, incl. * wildcards — a @revoked * org-wide revocation matches every host).lib/net/ssh/known_hosts.rb:265-270:keys <<
if marker == "@cert-authority"
HostKeyEntries::CertAuthority.new(raw_key, comment: comment)
else
HostKeyEntries::PubKey.new(raw_key, comment: comment) # <-- "@revoked" lands here as a TRUSTED pin
endlib/net/ssh/verifiers/always.rb:19-25 — found_key = host_keys.find { ... }; PubKey#matches_key?
(lib/net/ssh/known_hosts.rb:31-33) compares ssh_type + to_blob → exact match with the revoked key.lib/net/ssh/verifiers/always.rb:32 + :56,65,70,75 — verify_certificate! guards each certificate
constraint with entry.respond_to?(...); PubKey responds to none → all checks skipped.lib/net/ssh/verifiers/always.rb:34 — returns true. abstract.rb:82 is satisfied; the signature check
(abstract.rb:107) succeeds because the attacker owns the revoked private key. Connection proceeds to
userauth with a host the user explicitly distrusted. (AcceptNew's rescue HostKeyUnknown path in
verifiers/accept_new.rb is not even needed — verification succeeded outright.)Host authentication bypass that opens a full SSH MITM. The attacker can read and inject session plaintext (commands, output, uploaded data), capture credentials sent over password auth, and take over deploy-tool command channels, since Capistrano, Kamal, and sshkit all sit on net-ssh. A server the operator explicitly distrusted is accepted as the real host, so everything the user does over that connection is exposed.
Source: NVD
Free Vulnerability Assessment
Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.
Get a personalized demo
"Best User Experience I have ever seen, provides full visibility to cloud workloads."
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
"We know that if Wiz identifies something as critical, it actually is."