diff options
Diffstat (limited to 'vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php')
-rw-r--r-- | vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php | 169 |
1 files changed, 152 insertions, 17 deletions
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php index 7ec4a1e36..607cc2145 100644 --- a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php +++ b/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php @@ -274,6 +274,18 @@ class SSH2 var $server_host_key_algorithms = false; /** + * Supported Private Key Algorithms + * + * In theory this should be the same as the Server Host Key Algorithms but, in practice, + * some servers (eg. Azure) will support rsa-sha2-512 as a server host key algorithm but + * not a private key algorithm + * + * @see self::privatekey_login() + * @var array|false + */ + var $supported_private_key_algorithms = false; + + /** * Encryption Algorithms: Client to Server * * @see self::getEncryptionAlgorithmsClient2Server() @@ -391,6 +403,14 @@ class SSH2 var $decrypt = false; /** + * Decryption Algorithm Name + * + * @var string|null + * @access private + */ + var $decryptName; + + /** * Client to Server Encryption Object * * @see self::_send_binary_packet() @@ -400,6 +420,14 @@ class SSH2 var $encrypt = false; /** + * Encryption Algorithm Name + * + * @var string|null + * @access private + */ + var $encryptName; + + /** * Client to Server HMAC Object * * @see self::_send_binary_packet() @@ -409,6 +437,13 @@ class SSH2 var $hmac_create = false; /** + * Client to Server HMAC Name + * + * @var string|false + */ + private $hmac_create_name; + + /** * Server to Client HMAC Object * * @see self::_get_binary_packet() @@ -418,6 +453,13 @@ class SSH2 var $hmac_check = false; /** + * Server to Client HMAC Name + * + * @var string|false + */ + var $hmac_check_name; + + /** * Size of server to client HMAC * * We need to know how big the HMAC will be for the server to client direction so that we know how many bytes to read. @@ -1055,9 +1097,19 @@ class SSH2 var $smartMFA = true; /** + * Extra packets counter + * + * @var bool + * @access private + */ + var $extra_packets; + + /** * Default Constructor. * * $host can either be a string, representing the host, or a stream resource. + * If $host is a stream resource then $port doesn't do anything, altho $timeout + * still will be used * * @param mixed $host * @param int $port @@ -1075,6 +1127,7 @@ class SSH2 4 => 'NET_SSH2_MSG_DEBUG', 5 => 'NET_SSH2_MSG_SERVICE_REQUEST', 6 => 'NET_SSH2_MSG_SERVICE_ACCEPT', + 7 => 'NET_SSH2_MSG_EXT_INFO', // RFC 8308 20 => 'NET_SSH2_MSG_KEXINIT', 21 => 'NET_SSH2_MSG_NEWKEYS', 30 => 'NET_SSH2_MSG_KEXDH_INIT', @@ -1147,6 +1200,8 @@ class SSH2 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY') ); + $this->timeout = $timeout; + if (is_resource($host)) { $this->fsock = $host; return; @@ -1155,7 +1210,6 @@ class SSH2 if (is_string($host)) { $this->host = $host; $this->port = $port; - $this->timeout = $timeout; } } @@ -1459,6 +1513,8 @@ class SSH2 $preferred['client_to_server']['comp'] : $this->getSupportedCompressionAlgorithms(); + $kex_algorithms = array_merge($kex_algorithms, array('ext-info-c', 'kex-strict-c-v00@openssh.com')); + // some SSH servers have buggy implementations of some of the above algorithms switch (true) { case $this->server_identifier == 'SSH-2.0-SSHD': @@ -1521,6 +1577,7 @@ class SSH2 return false; } + $this->extra_packets = 0; $kexinit_payload_server = $this->_get_binary_packet(); if ($kexinit_payload_server === false) { $this->bitmap = 0; @@ -1545,6 +1602,12 @@ class SSH2 } $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); + if (in_array('kex-strict-s-v00@openssh.com', $this->kex_algorithms)) { + if ($this->session_id === false && $this->extra_packets) { + user_error('Possible Terrapin Attack detected'); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + } if (strlen($response) < 4) { return false; @@ -1552,6 +1615,8 @@ class SSH2 $temp = unpack('Nlength', $this->_string_shift($response, 4)); $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); + $this->supported_private_key_algorithms = $this->server_host_key_algorithms; + if (strlen($response) < 4) { return false; } @@ -1949,6 +2014,10 @@ class SSH2 return false; } + if (in_array('kex-strict-s-v00@openssh.com', $this->kex_algorithms)) { + $this->get_seq_no = $this->send_seq_no = 0; + } + $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes); $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt); @@ -1978,7 +2047,7 @@ class SSH2 } $this->encrypt->setKey(substr($key, 0, $encryptKeyLength)); - $this->encrypt->name = $decrypt; + $this->encryptName = $encrypt; } $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt); @@ -2008,7 +2077,7 @@ class SSH2 } $this->decrypt->setKey(substr($key, 0, $decryptKeyLength)); - $this->decrypt->name = $decrypt; + $this->decryptName = $decrypt; } /* The "arcfour128" algorithm is the RC4 cipher, as described in @@ -2053,7 +2122,7 @@ class SSH2 $this->hmac_create = new Hash('md5-96'); $createKeyLength = 16; } - $this->hmac_create->name = $mac_algorithm_out; + $this->hmac_create_name = $mac_algorithm_out; $checkKeyLength = 0; $this->hmac_size = 0; @@ -2083,7 +2152,7 @@ class SSH2 $checkKeyLength = 16; $this->hmac_size = 12; } - $this->hmac_check->name = $mac_algorithm_in; + $this->hmac_check_name = $mac_algorithm_in; $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id); while ($createKeyLength > strlen($key)) { @@ -2223,7 +2292,9 @@ class SSH2 function login($username) { $args = func_get_args(); - $this->auth[] = $args; + if (!$this->retry_connect) { + $this->auth[] = $args; + } // try logging with 'none' as an authentication method first since that's what // PuTTY does @@ -2366,6 +2437,35 @@ class SSH2 } extract(unpack('Ctype', $this->_string_shift($response, 1))); + if ($type == NET_SSH2_MSG_EXT_INFO) { + if (strlen($response) < 4) { + return false; + } + $nr_extensions = unpack('Nlength', $this->_string_shift($response, 4)); + for ($i = 0; $i < $nr_extensions['length']; $i++) { + if (strlen($response) < 4) { + return false; + } + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $extension_name = $this->_string_shift($response, $temp['length']); + if ($extension_name == 'server-sig-algs') { + if (strlen($response) < 4) { + return false; + } + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->supported_private_key_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); + } + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + $this->bitmap = 0; + user_error('Connection closed by server'); + return false; + } + extract(unpack('Ctype', $this->_string_shift($response, 1))); + } + if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) { user_error('Expected SSH_MSG_SERVICE_ACCEPT'); return false; @@ -2731,7 +2831,13 @@ class SSH2 $publickey['n'] ); - switch ($this->signature_format) { + $algos = array('rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa'); + if (isset($this->preferred['hostkey'])) { + $algos = array_intersect($algos, $this->preferred['hostkey']); + } + $algo = $this->_array_intersect_first($algos, $this->supported_private_key_algorithms); + + switch ($algo) { case 'rsa-sha2-512': $hash = 'sha512'; $signatureType = 'rsa-sha2-512'; @@ -2781,7 +2887,12 @@ class SSH2 return false; } extract(unpack('Nmethodlistlen', $this->_string_shift($response, 4))); - $this->auth_methods_to_continue = explode(',', $this->_string_shift($response, $methodlistlen)); + $auth_methods = explode(',', $this->_string_shift($response, $methodlistlen)); + if (in_array('publickey', $auth_methods) && substr($signatureType, 0, 9) == 'rsa-sha2-') { + $this->supported_private_key_algorithms = array_diff($this->supported_private_key_algorithms, array('rsa-sha2-256', 'rsa-sha2-512')); + return $this->_privatekey_login($username, $privatekey); + } + $this->auth_methods_to_continue = $auth_methods; $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE'; return false; case NET_SSH2_MSG_USERAUTH_PK_OK: @@ -2836,6 +2947,16 @@ class SSH2 } /** + * Return the currently configured timeout + * + * @return int + */ + function getTimeout() + { + return $this->timeout; + } + + /** * Set Timeout * * $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely. setTimeout() makes it so it'll timeout. @@ -3363,7 +3484,7 @@ class SSH2 */ function isConnected() { - return (bool) ($this->bitmap & self::MASK_CONNECTED); + return ($this->bitmap & self::MASK_CONNECTED) && is_resource($this->fsock) && !feof($this->fsock); } /** @@ -3519,11 +3640,18 @@ class SSH2 if (!is_resource($this->fsock) || feof($this->fsock)) { $this->bitmap = 0; - user_error('Connection closed (by server) prematurely ' . $elapsed . 's'); + $str = 'Connection closed (by server) prematurely'; + if (isset($elapsed)) { + $str.= ' ' . $elapsed . 's'; + } + user_error($str); return false; } $start = microtime(true); + $sec = (int) floor($this->curTimeout); + $usec = (int) (1000000 * ($this->curTimeout - $sec)); + stream_set_timeout($this->fsock, $sec, $usec); $raw = stream_get_contents($this->fsock, $this->decrypt_block_size); if (!strlen($raw)) { @@ -3550,7 +3678,7 @@ class SSH2 // "implementations SHOULD check that the packet length is reasonable" // PuTTY uses 0x9000 as the actual max packet size and so to shall we if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) { - if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decrypt->name) && !($this->bitmap & SSH2::MASK_LOGIN)) { + if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decryptName) && !($this->bitmap & SSH2::MASK_LOGIN)) { $this->bad_key_size_fix = true; $this->_reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); return false; @@ -3663,9 +3791,11 @@ class SSH2 $this->bitmap = 0; return false; case NET_SSH2_MSG_IGNORE: + $this->extra_packets++; $payload = $this->_get_binary_packet($skip_channel_filter); break; case NET_SSH2_MSG_DEBUG: + $this->extra_packets++; $this->_string_shift($payload, 2); if (strlen($payload) < 4) { return false; @@ -3677,6 +3807,7 @@ class SSH2 case NET_SSH2_MSG_UNIMPLEMENTED: return false; case NET_SSH2_MSG_KEXINIT: + // this is here for key re-exchanges after the initial key exchange if ($this->session_id !== false) { $this->send_kex_first = false; if (!$this->_key_exchange($payload)) { @@ -4573,7 +4704,9 @@ class SSH2 } /** - * Returns all errors + * Returns all errors / debug messages on the SSH layer + * + * If you are looking for messages from the SFTP layer, please see SFTP::getSFTPErrors() * * @return string[] * @access public @@ -4584,7 +4717,9 @@ class SSH2 } /** - * Returns the last error + * Returns the last error received on the SSH layer + * + * If you are looking for messages from the SFTP layer, please see SFTP::getLastSFTPError() * * @return string * @access public @@ -4955,13 +5090,13 @@ class SSH2 'kex' => $this->kex_algorithm, 'hostkey' => $this->signature_format, 'client_to_server' => array( - 'crypt' => $this->encrypt->name, - 'mac' => $this->hmac_create->name, + 'crypt' => $this->encryptName, + 'mac' => $this->hmac_create_name, 'comp' => $compression_map[$this->compress], ), 'server_to_client' => array( - 'crypt' => $this->decrypt->name, - 'mac' => $this->hmac_check->name, + 'crypt' => $this->decryptName, + 'mac' => $this->hmac_check_name, 'comp' => $compression_map[$this->decompress], ) ); |