diff options
author | Pierce Lopez <pierce.lopez@gmail.com> | 2017-02-20 15:36:29 -0500 |
---|---|---|
committer | Pierce Lopez <pierce.lopez@gmail.com> | 2017-09-13 12:16:01 -0400 |
commit | 9720259c11cd430c095ff332090ebb6b09bd248d (patch) | |
tree | 08e0662429c63bfa5183abb49aeba2bde84ff569 | |
parent | 97a0aaa09531905aa95fce41e20e76994782ffed (diff) | |
download | paramiko-9720259c11cd430c095ff332090ebb6b09bd248d.tar.gz |
SSHClient: adjust Transport preferred host key types if known host
If we have a host keys that will be checked, we need to
negotiate for the type we have. Commonly, openssh could
have saved an ecdsa key in known_hosts, but SSHClient will
let the Transport negotiate for an rsa key.
Then it would consider a key of a non-corresponding type to be "missing".
That situation is also now a BadHostKeyException.
Before this change, a man-in-the-middle attack on the paramiko ssh
client was possible by having only a host key type which differs from
what the client has in known_hosts (and then giving any key of that type).
-rw-r--r-- | paramiko/client.py | 38 |
1 files changed, 21 insertions, 17 deletions
diff --git a/paramiko/client.py b/paramiko/client.py index a5291545..939569a1 100644 --- a/paramiko/client.py +++ b/paramiko/client.py @@ -334,34 +334,38 @@ class SSHClient (ClosingContextManager): t.set_log_channel(self._log_channel) if banner_timeout is not None: t.banner_timeout = banner_timeout - t.start_client(timeout=timeout) - - server_key = t.get_remote_server_key() - keytype = server_key.get_name() if port == SSH_PORT: server_hostkey_name = hostname else: server_hostkey_name = "[%s]:%d" % (hostname, port) + our_server_keys = None + + our_server_keys = self._system_host_keys.get(server_hostkey_name) + if our_server_keys is None: + our_server_keys = self._host_keys.get(server_hostkey_name) + if our_server_keys is not None: + keytype = our_server_keys.keys()[0] + sec_opts = t.get_security_options() + other_types = [x for x in sec_opts.key_types if x != keytype] + sec_opts.key_types = [keytype] + other_types + + t.start_client(timeout=timeout) # If GSS-API Key Exchange is performed we are not required to check the # host key, because the host is authenticated via GSS-API / SSPI as # well as our client. if not self._transport.gss_kex_used: - our_server_key = self._system_host_keys.get(server_hostkey_name, - {}).get(keytype, None) - if our_server_key is None: - our_server_key = self._host_keys.get(server_hostkey_name, - {}).get(keytype, None) - if our_server_key is None: + server_key = t.get_remote_server_key() + if our_server_keys is None: # will raise exception if the key is rejected; let that fall out - self._policy.missing_host_key(self, server_hostkey_name, - server_key) - # if the callback returns, assume the key is ok - our_server_key = server_key - - if server_key != our_server_key: - raise BadHostKeyException(hostname, server_key, our_server_key) + self._policy.missing_host_key(self, server_hostkey_name, server_key) + else: + our_key = our_server_keys.get(server_key.get_name()) + if our_key != server_key: + if our_key is None: + our_key = list(our_server_keys.values())[0] + raise BadHostKeyException(hostname, server_key, our_key) if username is None: username = getpass.getuser() |