I did this
I found that when libcurl’s SSH/SFTP support connects to a host on a non-standard port and the user accepts its host key, the key is written to known_hosts as a plain hostname. OpenSSH instead records it as [host]:port.
This means trust isn’t scoped to the port where the key was accepted. If you accept a key for example.com:2222, libcurl will later trust that same key for example.com on any port, including 22.
I think the root cause is that lookup and storage use different rules. Verification uses libssh2_knownhost_checkp() and passes the port, but accepted keys are added via libssh2_knownhost_add() with only the hostname. So port-specific lookups can match entries that were never stored with a port in the first place.
I see this more as a hardening issue than a practical security vulnerability because the attack requires a fairly specific setup though it does differ from OpenSSH’s trust model and can weaken existing host-key pinning. An attacker who can get a different key accepted on another SSH port of the same host can cause libcurl to store that key as a host-wide entry, after which it may be silently accepted for connections to port 22 as well.
I expected the following
I expected libcurl to scope the stored entry to the port the key was accepted on, the
same way OpenSSH does — writing [host]:port for non-default ports — so that a key accepted
for example.com:2222 is only trusted for example.com:2222, and a later connection to
example.com:22 that presents that key is rejected rather than silently accepted.
In other words, I expected storage and lookup to agree: the verification path already passes
the port to libssh2_knownhost_checkp(), so the add path should record the port too, and
port-scoped lookups would then only match entries actually stored for that port.
curl/libcurl version
current master (curl-8_20_0-254-g6ac42e5691), libssh2 backend
operating system
Ubuntu 22.04
I did this
I found that when libcurl’s SSH/SFTP support connects to a host on a non-standard port and the user accepts its host key, the key is written to known_hosts as a plain hostname. OpenSSH instead records it as [host]:port.
This means trust isn’t scoped to the port where the key was accepted. If you accept a key for example.com:2222, libcurl will later trust that same key for example.com on any port, including 22.
I think the root cause is that lookup and storage use different rules. Verification uses libssh2_knownhost_checkp() and passes the port, but accepted keys are added via libssh2_knownhost_add() with only the hostname. So port-specific lookups can match entries that were never stored with a port in the first place.
I see this more as a hardening issue than a practical security vulnerability because the attack requires a fairly specific setup though it does differ from OpenSSH’s trust model and can weaken existing host-key pinning. An attacker who can get a different key accepted on another SSH port of the same host can cause libcurl to store that key as a host-wide entry, after which it may be silently accepted for connections to port 22 as well.
I expected the following
I expected libcurl to scope the stored entry to the port the key was accepted on, the
same way OpenSSH does — writing [host]:port for non-default ports — so that a key accepted
for example.com:2222 is only trusted for example.com:2222, and a later connection to
example.com:22 that presents that key is rejected rather than silently accepted.
In other words, I expected storage and lookup to agree: the verification path already passes
the port to libssh2_knownhost_checkp(), so the add path should record the port too, and
port-scoped lookups would then only match entries actually stored for that port.
curl/libcurl version
current master (
curl-8_20_0-254-g6ac42e5691), libssh2 backendoperating system
Ubuntu 22.04