HexBrain

curl · Report #4112709

CVE-2026-111482: libssh2 knownhosts prefix match

Validstatus
lowseverity
1upvotes
26 Oct 2026disclosed

How it was found

AI Disclaimer:

I've written this report with the help of AI, please feel free to let me know if I made a mistake, I've read the final revision of this report a few times to ensure it was solid but my understanding of the inner workings of curl and libssh is where I can fall short.

Summary:

ssh_force_knownhost_key_type() in lib/vssh/libssh2.c compares a [name]:port known_hosts entry, the form ssh writes for non-default ports, with strncmp(store->name + 1, conn->origin->hostname, kh_name_size). kh_name_size is the length of the name in the entry, so this is a prefix match: [10.0.0.1]:2222 also matches 10.0.0.12, and [git.example.co]:2222 also matches git.example.com. If such an entry for another host comes before the host's own entry with a different key type, curl forces that other key type for the connection. An attacker's server simply offers that type. The later check in ssh_knownhost() then finds no entry of that type for the host: with libssh2 1.11.1, libssh2_knownhost_checkp() only reports MISMATCH for a key of the same type. CURLOPT_SSH_KEYFUNCTION is therefore told CURLKHMATCH_MISSING instead of CURLKHMATCH_MISMATCH, which is the outcome of CVE-2026-87865, reached through plain entries instead of hashed ones; the code comes from 272282a054 (curl 7.69.0).

I would have liked to report this sooner, before 8.23.0 was out, but I've only managed to finish this now and I still have a lot of other curl CVEs to fix for the Debian package.

Affected version

Current master, 9a6d4ffc1 (2026-10-13), debug build on Debian forky/sid x86_64, with libssh2 1.11.1. The same code has been there since 7.69.0.

curl 8.23.0-DEV (x86_64-pc-linux-gnu) libcurl/8.23.0-DEV OpenSSL/3.6.5 zlib/1.3.2 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libssh2/1.11.1 nghttp2/1.70.0 OpenLDAP/2.6.14
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt mqtts pop3 pop3s rtsp scp sftp smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli Debug HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz SSL threadsafe UnixSockets zstd

Steps To Reproduce:

Copy the attached test3990.pl to tests/libtest/, and test3990 and test3991 to tests/data/. test3990 runs against the test suite's SFTP server, which has an RSA host key by default, with this known_hosts file:

[127.0.0]:PORT ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAqmXRY8k+W9GtpWxYru6ELAXYHdXqjf33ziITGTiJUN
[127.0.0.1]:PORT ssh-rsa <the server's real key>
cd tests && ./runtests.pl 3990 3991

test3990 fails, and its trace shows curl taking the [127.0.0] entry for 127.0.0.1:

SSH: found host '127.0.0.1' in 'log/known3990'
SSH: set 'ssh-ed25519' as hostkey type
Failure establishing ssh session: -5, Unable to exchange encryption keys

The handshake fails only because the test server has no ed25519 key; a man-in-the-middle server would offer one. test3991 is the control. Its first entry is for [127.0.0.1.2]:PORT, which is not a prefix of the host, so curl forces ssh-rsa and the transfer works.

Impact

Summary:

An attacker who can intercept connections can target a host whose name extends an earlier [name]:port entry with the same port, which is most likely with IP addresses such as 10.0.0.1 and 10.0.0.12. An application whose CURLOPT_SSH_KEYFUNCTION callback trusts new hosts would then accept the attacker's key instead of rejecting a mismatch, and authenticate to the attacker. The conditions are otherwise those of CVE-2026-87865: the libssh2 backend and an application-provided key callback. Without an attacker it is also a functional bug. A legitimate connection to such a host fails, either at key exchange or because its key is reported as missing, as in the test above.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original