diff --git a/README.md b/README.md index 564e8127..7fafbe9f 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,37 @@ MIT-licensed pure-PHP implementations of an arbitrary-precision integer arithmetic library, fully PKCS#1 (v2.1) compliant RSA, DES, 3DES, RC4, Rijndael, AES, Blowfish, Twofish, SSH-1, SSH-2, SFTP, and X.509 -* [Download (1.0.5)](http://sourceforge.net/projects/phpseclib/files/phpseclib1.0.5.zip/download) * [Browse Git](https://github.com/phpseclib/phpseclib) -* [Code Coverage Report](http://phpseclib.bantux.org/code_coverage/master/latest/) - -PEAR Channel -PEAR Channel: [phpseclib.sourceforge.net](http://phpseclib.sourceforge.net/pear.htm) +* [Code Coverage Report](https://coverage.phpseclib.org/master/latest/) ## Documentation * [Documentation / Manual](http://phpseclib.sourceforge.net/) -* [API Documentation](http://phpseclib.bantux.org/api/master/) (generated by Sami) +* [API Documentation](https://api.phpseclib.org/master/) (generated by Sami) + +## Branches + +### master + +* Development Branch +* Unstable API +* Do not use in production + +### 2.0 + +* Modernized version of 1.0 +* Minimum PHP version: 5.3.3 +* PSR-4 autoloading with namespace rooted at `\phpseclib` +* Install via Composer: `composer require phpseclib/phpseclib ~2.0` + +### 1.0 + +* Long term support (LTS) release +* PHP4 compatible +* Composer compatible (PSR-0 autoloading) +* Install using Composer: `composer require phpseclib/phpseclib ~1.0` +* Install using PEAR: See [phpseclib PEAR Channel Documentation](http://phpseclib.sourceforge.net/pear.htm) +* [Download 1.0.5 as ZIP](http://sourceforge.net/projects/phpseclib/files/phpseclib1.0.5.zip/download) ## Support @@ -26,40 +46,29 @@ Need Support? * [Create a Support Ticket on GitHub](https://github.com/phpseclib/phpseclib/issues/new) * [Browse the Support Forum](http://www.frostjedi.com/phpbb/viewforum.php?f=46) (no longer in use) -## Installing Development Dependencies - -Dependencies are managed via Composer. - -1. Download the [`composer.phar`](https://getcomposer.org/composer.phar) executable as per the - [Composer Download Instructions](https://getcomposer.org/download/), e.g. by running - - ``` sh - curl -sS https://getcomposer.org/installer | php - ``` - -2. Install Dependencies - - ``` sh - php composer.phar install - ``` - ## Contributing 1. Fork the Project -2. Install Development Dependencies +2. Ensure you have Composer installed (see [Composer Download Instructions](https://getcomposer.org/download/)) -3. Create a Feature Branch +3. Install Development Dependencies -4. (Recommended) Run the Test Suite + ``` sh + composer install + ``` + +4. Create a Feature Branch + +5. (Recommended) Run the Test Suite ``` sh vendor/bin/phpunit ``` -5. (Recommended) Check whether your code conforms to our Coding Standards by running +6. (Recommended) Check whether your code conforms to our Coding Standards by running ``` sh vendor/bin/phing -f build/build.xml sniff ``` -6. Send us a Pull Request +7. Send us a Pull Request diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index ab1b6415..5286341e 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -885,6 +885,9 @@ class X509 // "SET Secure Electronic Transaction Specification" // http://www.maithean.com/docs/set_bk3.pdf case '2.23.42.7.0': // id-set-hashedRootKey + // "Certificate Transparency" + // https://tools.ietf.org/html/rfc6962 + case '1.3.6.1.4.1.11129.2.4.2': return true; // CSR attributes diff --git a/phpseclib/Net/SCP.php b/phpseclib/Net/SCP.php index e1f31e3c..78d46a0f 100755 --- a/phpseclib/Net/SCP.php +++ b/phpseclib/Net/SCP.php @@ -304,6 +304,9 @@ class SCP $response = Objects::getFunc($this->ssh, 'get_binary_packet'); switch ($response[SSH1::RESPONSE_TYPE]) { case NET_SSH1_SMSG_STDOUT_DATA: + if (strlen($response[SSH1::RESPONSE_DATA]) < 4) { + return false; + } extract(unpack('Nlength', $response[SSH1::RESPONSE_DATA])); return Strings::shift($response[SSH1::RESPONSE_DATA], $length); case NET_SSH1_SMSG_STDERR_DATA: diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index 7d41893e..ab081a2a 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -376,7 +376,7 @@ class SFTP extends SSH2 ); if (!defined('NET_SFTP_QUEUE_SIZE')) { - define('NET_SFTP_QUEUE_SIZE', 50); + define('NET_SFTP_QUEUE_SIZE', 32); } } @@ -392,7 +392,7 @@ class SFTP extends SSH2 public function login($username) { $args = func_get_args(); - if (!call_user_func_array(array(&$this, 'sublogin'), $args)) { + if (!call_user_func_array([&$this, 'sublogin'], $args)) { return false; } @@ -476,11 +476,20 @@ class SFTP extends SSH2 throw new \UnexpectedValueException('Expected SSH_FXP_VERSION'); } + if (strlen($response) < 4) { + return false; + } extract(unpack('Nversion', Strings::shift($response, 4))); $this->version = $version; while (!empty($response)) { + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $key = Strings::shift($response, $length); + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $value = Strings::shift($response, $length); $this->extensions[$key] = $value; @@ -591,12 +600,15 @@ class SFTP extends SSH2 private function logError($response, $status = -1) { if ($status == -1) { + if (strlen($response) < 4) { + return; + } extract(unpack('Nstatus', Strings::shift($response, 4))); } $error = $this->status_codes[$status]; - if ($this->version > 2) { + if ($this->version > 2 || strlen($response) < 4) { extract(unpack('Nlength', Strings::shift($response, 4))); $this->sftp_errors[] = $error . ': ' . Strings::shift($response, $length); } else { @@ -631,6 +643,9 @@ class SFTP extends SSH2 // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks // at is the first part and that part is defined the same in SFTP versions 3 through 6. Strings::shift($response, 4); // skip over the count - it should be 1, anyway + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); return Strings::shift($response, $length); case NET_SFTP_STATUS: @@ -864,10 +879,19 @@ class SFTP extends SSH2 $response = $this->get_sftp_packet(); switch ($this->packet_type) { case NET_SFTP_NAME: + if (strlen($response) < 4) { + return false; + } extract(unpack('Ncount', Strings::shift($response, 4))); for ($i = 0; $i < $count; $i++) { + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $shortname = Strings::shift($response, $length); + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $longname = Strings::shift($response, $length); $attributes = $this->parseAttributes($response); @@ -894,6 +918,9 @@ class SFTP extends SSH2 } break; case NET_SFTP_STATUS: + if (strlen($response) < 4) { + return false; + } extract(unpack('Nstatus', Strings::shift($response, 4))); if ($status != NET_SFTP_STATUS_EOF) { $this->logError($response, $status); @@ -1487,6 +1514,9 @@ class SFTP extends SSH2 throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } + if (strlen($response) < 4) { + return false; + } extract(unpack('Nstatus', Strings::shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->logError($response, $status); @@ -1599,12 +1629,18 @@ class SFTP extends SSH2 throw new \UnexpectedValueException('Expected SSH_FXP_NAME or SSH_FXP_STATUS'); } + if (strlen($response) < 4) { + return false; + } extract(unpack('Ncount', Strings::shift($response, 4))); // the file isn't a symlink if (!$count) { return false; } + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); return Strings::shift($response, $length); } @@ -1639,7 +1675,11 @@ class SFTP extends SSH2 throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } + if (strlen($response) < 4) { + return false; + } extract(unpack('Nstatus', Strings::shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { $this->logError($response, $status); return false; @@ -1702,6 +1742,9 @@ class SFTP extends SSH2 throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } + if (strlen($response) < 4) { + return false; + } extract(unpack('Nstatus', Strings::shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->logError($response, $status); @@ -1739,6 +1782,9 @@ class SFTP extends SSH2 throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } + if (strlen($response) < 4) { + return false; + } extract(unpack('Nstatus', Strings::shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED? @@ -1965,6 +2011,9 @@ class SFTP extends SSH2 throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } + if (strlen($response) < 4) { + return false; + } extract(unpack('Nstatus', Strings::shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->logError($response, $status); @@ -1996,6 +2045,9 @@ class SFTP extends SSH2 throw new \UnexpectedValueException('Expected SSH_FXP_STATUS'); } + if (strlen($response) < 4) { + return false; + } extract(unpack('Nstatus', Strings::shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->logError($response, $status); @@ -2170,6 +2222,15 @@ class SFTP extends SSH2 return false; } + if (is_object($path)) { + // It's an object. Cast it as string before we check anything else. + $path = (string) $path; + } + + if (!is_string($path) || $path == '') { + return false; + } + $path = $this->realpath($path); if ($path === false) { return false; @@ -2186,6 +2247,9 @@ class SFTP extends SSH2 } // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED + if (strlen($response) < 4) { + return false; + } extract(unpack('Nstatus', Strings::shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->logError($response, $status); @@ -2611,6 +2675,9 @@ class SFTP extends SSH2 } // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED + if (strlen($response) < 4) { + return false; + } extract(unpack('Nstatus', Strings::shift($response, 4))); if ($status != NET_SFTP_STATUS_OK) { $this->logError($response, $status); @@ -2638,6 +2705,10 @@ class SFTP extends SSH2 private function parseAttributes(&$response) { $attr = []; + if (strlen($response) < 4) { + //user_error('Malformed file attributes'); + return []; + } extract(unpack('Nflags', Strings::shift($response, 4))); // SFTPv4+ have a type field (a byte) that follows the above flag field foreach ($this->attributes as $key => $value) { @@ -2652,9 +2723,17 @@ class SFTP extends SSH2 $attr['size'] = hexdec(Hex::encode(Strings::shift($response, 8))); break; case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only) + if (strlen($response) < 8) { + //user_error('Malformed file attributes'); + return $attr; + } $attr+= unpack('Nuid/Ngid', Strings::shift($response, 8)); break; case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004 + if (strlen($response) < 4) { + //user_error('Malformed file attributes'); + return $attr; + } $attr+= unpack('Npermissions', Strings::shift($response, 4)); // mode == permissions; permissions was the original array key and is retained for bc purposes. // mode was added because that's the more industry standard terminology @@ -2665,13 +2744,29 @@ class SFTP extends SSH2 } break; case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008 + if (strlen($response) < 8) { + //user_error('Malformed file attributes'); + return $attr; + } $attr+= unpack('Natime/Nmtime', Strings::shift($response, 8)); break; case NET_SFTP_ATTR_EXTENDED: // 0x80000000 + if (strlen($response) < 4) { + //user_error('Malformed file attributes'); + return $attr; + } extract(unpack('Ncount', Strings::shift($response, 4))); for ($i = 0; $i < $count; $i++) { + if (strlen($response) < 4) { + //user_error('Malformed file attributes'); + return $attr; + } extract(unpack('Nlength', Strings::shift($response, 4))); $key = Strings::shift($response, $length); + if (strlen($response) < 4) { + //user_error('Malformed file attributes'); + return $attr; + } extract(unpack('Nlength', Strings::shift($response, 4))); $attr[$key] = Strings::shift($response, $length); } @@ -2825,6 +2920,9 @@ class SFTP extends SSH2 } $this->packet_buffer.= $temp; } + if (strlen($this->packet_buffer) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($this->packet_buffer, 4))); $tempLength = $length; $tempLength-= strlen($this->packet_buffer); diff --git a/phpseclib/Net/SSH1.php b/phpseclib/Net/SSH1.php index 4ea53369..edb4b5f3 100644 --- a/phpseclib/Net/SSH1.php +++ b/phpseclib/Net/SSH1.php @@ -575,28 +575,46 @@ class SSH1 Strings::shift($response[self::RESPONSE_DATA], 4); + if (strlen($response[self::RESPONSE_DATA]) < 2) { + return false; + } $temp = unpack('nlen', Strings::shift($response[self::RESPONSE_DATA], 2)); $server_key_public_exponent = new BigInteger(Strings::shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->server_key_public_exponent = $server_key_public_exponent; + if (strlen($response[self::RESPONSE_DATA]) < 2) { + return false; + } $temp = unpack('nlen', Strings::shift($response[self::RESPONSE_DATA], 2)); $server_key_public_modulus = new BigInteger(Strings::shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); + $this->server_key_public_modulus = $server_key_public_modulus; Strings::shift($response[self::RESPONSE_DATA], 4); + if (strlen($response[self::RESPONSE_DATA]) < 2) { + return false; + } $temp = unpack('nlen', Strings::shift($response[self::RESPONSE_DATA], 2)); $host_key_public_exponent = new BigInteger(Strings::shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); $this->host_key_public_exponent = $host_key_public_exponent; + if (strlen($response[self::RESPONSE_DATA]) < 2) { + return false; + } $temp = unpack('nlen', Strings::shift($response[self::RESPONSE_DATA], 2)); $host_key_public_modulus = new BigInteger(Strings::shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256); + $this->host_key_public_modulus = $host_key_public_modulus; Strings::shift($response[self::RESPONSE_DATA], 4); // get a list of the supported ciphers + if (strlen($response[self::RESPONSE_DATA]) < 4) { + return false; + } extract(unpack('Nsupported_ciphers_mask', Strings::shift($response[self::RESPONSE_DATA], 4))); + foreach ($this->supported_ciphers as $mask => $name) { if (($supported_ciphers_mask & (1 << $mask)) == 0) { unset($this->supported_ciphers[$mask]); @@ -604,6 +622,9 @@ class SSH1 } // get a list of the supported authentications + if (strlen($response[self::RESPONSE_DATA]) < 4) { + return false; + } extract(unpack('Nsupported_authentications_mask', Strings::shift($response[self::RESPONSE_DATA], 4))); foreach ($this->supported_authentications as $mask => $name) { if (($supported_authentications_mask & (1 << $mask)) == 0) { @@ -1085,7 +1106,11 @@ class SSH1 } $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 - $temp = unpack('Nlength', fread($this->fsock, 4)); + $data = fread($this->fsock, 4); + if (strlen($data) < 4) { + return false; + } + $temp = unpack('Nlength', $data); $padding_length = 8 - ($temp['length'] & 7); $length = $temp['length'] + $padding_length; @@ -1106,6 +1131,9 @@ class SSH1 $type = $raw[$padding_length]; $data = substr($raw, $padding_length + 1, -4); + if (strlen($raw) < 4) { + return false; + } $temp = unpack('Ncrc', substr($raw, -4)); //if ( $temp['crc'] != $this->crc($padding . $type . $data) ) { diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 9a38f561..9ce0203e 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -1021,7 +1021,10 @@ class SSH2 if (!is_resource($this->fsock)) { $start = microtime(true); - $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout); + // with stream_select a timeout of 0 means that no timeout takes place; + // with fsockopen a timeout of 0 means that you instantly timeout + // to resolve this incompatibility a timeout of 100,000 will be used for fsockopen if timeout is 0 + $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout == 0 ? 100000 : $this->curTimeout); if (!$this->fsock) { $host = $this->host . ':' . $this->port; throw new \RuntimeException(rtrim("Cannot connect to $host. Error $errno. $errstr")); @@ -1119,7 +1122,7 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } - if (ord($response[0]) != NET_SSH2_MSG_KEXINIT) { + if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) { throw new \UnexpectedValueException('Expected SSH_MSG_KEXINIT'); } @@ -1318,37 +1321,71 @@ class SSH2 Strings::shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT) $server_cookie = Strings::shift($response, 16); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->kex_algorithms = explode(',', Strings::shift($response, $temp['length'])); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->server_host_key_algorithms = explode(',', Strings::shift($response, $temp['length'])); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->encryption_algorithms_client_to_server = explode(',', Strings::shift($response, $temp['length'])); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->encryption_algorithms_server_to_client = explode(',', Strings::shift($response, $temp['length'])); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->mac_algorithms_client_to_server = explode(',', Strings::shift($response, $temp['length'])); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->mac_algorithms_server_to_client = explode(',', Strings::shift($response, $temp['length'])); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->compression_algorithms_client_to_server = explode(',', Strings::shift($response, $temp['length'])); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->compression_algorithms_server_to_client = explode(',', Strings::shift($response, $temp['length'])); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->languages_client_to_server = explode(',', Strings::shift($response, $temp['length'])); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->languages_server_to_client = explode(',', Strings::shift($response, $temp['length'])); + if (!strlen($response)) { + return false; + } extract(unpack('Cfirst_kex_packet_follows', Strings::shift($response, 1))); + $first_kex_packet_follows = $first_kex_packet_follows != 0; // the sending of SSH2_MSG_KEXINIT could go in one of two places. this is the second place. @@ -1447,10 +1484,16 @@ class SSH2 return false; } + if (strlen($response) < 4) { + return false; + } extract(unpack('NprimeLength', Strings::shift($response, 4))); $primeBytes = Strings::shift($response, $primeLength); $prime = new BigInteger($primeBytes, -256); + if (strlen($response) < 4) { + return false; + } extract(unpack('NgLength', Strings::shift($response, 4))); $gBytes = Strings::shift($response, $gLength); $g = new BigInteger($gBytes, -256); @@ -1531,24 +1574,42 @@ class SSH2 if ($response === false) { throw new \RuntimeException('Connection closed by server'); } + if (!strlen($response)) { + return false; + } extract(unpack('Ctype', Strings::shift($response, 1))); if ($type != $serverKexReplyMessage) { throw new \UnexpectedValueException('Expected SSH_MSG_KEXDH_REPLY'); } + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->server_public_host_key = $server_public_host_key = Strings::shift($response, $temp['length']); + if (strlen($server_public_host_key) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $public_key_format = Strings::shift($server_public_host_key, $temp['length']); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $fBytes = Strings::shift($response, $temp['length']); + if (strlen($response) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($response, 4)); $this->signature = Strings::shift($response, $temp['length']); + if (strlen($this->signature) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($this->signature, 4)); $this->signature_format = Strings::shift($this->signature, $temp['length']); @@ -1618,6 +1679,9 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } + if (!strlen($response)) { + return false; + } extract(unpack('Ctype', Strings::shift($response, 1))); if ($type != NET_SSH2_MSG_NEWKEYS) { @@ -1952,6 +2016,9 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } + if (strlen($response) < 4) { + return false; + } extract(unpack('Ctype', Strings::shift($response, 1))); if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) { @@ -1999,6 +2066,9 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } + if (!strlen($response)) { + return false; + } extract(unpack('Ctype', Strings::shift($response, 1))); switch ($type) { @@ -2053,6 +2123,9 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } + if (!strlen($response)) { + return false; + } extract(unpack('Ctype', Strings::shift($response, 1))); switch ($type) { @@ -2060,14 +2133,25 @@ class SSH2 if (defined('NET_SSH2_LOGGING')) { $this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'; } + + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . utf8_decode(Strings::shift($response, $length)); + return $this->disconnect_helper(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); case NET_SSH2_MSG_USERAUTH_FAILURE: // can we use keyboard-interactive authentication? if not then either the login is bad or the server employees // multi-factor authentication + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $auth_methods = explode(',', Strings::shift($response, $length)); + if (!strlen($response)) { + return false; + } extract(unpack('Cpartial_success', Strings::shift($response, 1))); $partial_success = $partial_success != 0; @@ -2142,16 +2226,31 @@ class SSH2 } } + if (!strlen($response)) { + return false; + } extract(unpack('Ctype', Strings::shift($response, 1))); switch ($type) { case NET_SSH2_MSG_USERAUTH_INFO_REQUEST: + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); Strings::shift($response, $length); // name; may be empty + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); Strings::shift($response, $length); // instruction; may be empty + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); Strings::shift($response, $length); // language tag; may be empty + if (strlen($response) < 4) { + return false; + } extract(unpack('Nnum_prompts', Strings::shift($response, 4))); for ($i = 0; $i < count($responses); $i++) { @@ -2166,6 +2265,9 @@ class SSH2 if (isset($this->keyboard_requests_responses)) { for ($i = 0; $i < $num_prompts; $i++) { + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); // prompt - ie. "Password: "; must not be empty $prompt = Strings::shift($response, $length); @@ -2311,12 +2413,19 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } + if (!strlen($response)) { + return false; + } extract(unpack('Ctype', Strings::shift($response, 1))); switch ($type) { case NET_SSH2_MSG_USERAUTH_FAILURE: + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE: ' . Strings::shift($response, $length); + return false; case NET_SSH2_MSG_USERAUTH_PK_OK: // we'll just take it on faith that the public key blob and the public key algorithm name are as @@ -2345,6 +2454,9 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } + if (!strlen($response)) { + return false; + } extract(unpack('Ctype', Strings::shift($response, 1))); switch ($type) { @@ -2401,7 +2513,12 @@ class SSH2 $this->is_timeout = false; $this->stdErrorLog = ''; - if (!($this->bitmap & self::MASK_LOGIN)) { + if (!$this->isAuthenticated()) { + return false; + } + + if ($this->in_request_pty_exec) { + user_error('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); return false; } @@ -2463,6 +2580,9 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } + if (!strlen($response)) { + return false; + } list(, $type) = unpack('C', Strings::shift($response, 1)); switch ($type) { @@ -2600,6 +2720,9 @@ class SSH2 throw new \RuntimeException('Connection closed by server'); } + if (!strlen($response)) { + return false; + } list(, $type) = unpack('C', Strings::shift($response, 1)); switch ($type) { @@ -2694,7 +2817,7 @@ class SSH2 $this->curTimeout = $this->timeout; $this->is_timeout = false; - if (!($this->bitmap & self::MASK_LOGIN)) { + if (!$this->isAuthenticated()) { throw new \RuntimeException('Operation disallowed prior to login()'); } @@ -2735,7 +2858,7 @@ class SSH2 */ public function write($cmd) { - if (!($this->bitmap & self::MASK_LOGIN)) { + if (!$this->isAuthenticated()) { throw new \RuntimeException('Operation disallowed prior to login()'); } @@ -2930,6 +3053,9 @@ class SSH2 $raw = $this->decrypt->decrypt($raw); } + if (strlen($raw) < 5) { + return false; + } extract(unpack('Npacket_length/Cpadding_length', Strings::shift($raw, 5))); $remaining_length = $packet_length + 4 - $this->decrypt_block_size; @@ -3001,6 +3127,9 @@ class SSH2 switch (ord($payload[0])) { case NET_SSH2_MSG_DISCONNECT: Strings::shift($payload, 1); + if (strlen($payload) < 8) { + return false; + } extract(unpack('Nreason_code/Nlength', Strings::shift($payload, 8))); $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . utf8_decode(Strings::shift($payload, $length)); $this->bitmap = 0; @@ -3010,6 +3139,9 @@ class SSH2 break; case NET_SSH2_MSG_DEBUG: Strings::shift($payload, 2); + if (strlen($payload) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($payload, 4))); $this->errors[] = 'SSH_MSG_DEBUG: ' . utf8_decode(Strings::shift($payload, $length)); $payload = $this->get_binary_packet(); @@ -3027,17 +3159,23 @@ class SSH2 } // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in - if (($this->bitmap & self::MASK_CONNECTED) && !($this->bitmap & self::MASK_LOGIN) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) { + if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) { Strings::shift($payload, 1); + if (strlen($payload) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($payload, 4))); $this->banner_message = utf8_decode(Strings::shift($payload, $length)); $payload = $this->get_binary_packet(); } // only called when we've already logged in - if (($this->bitmap & self::MASK_CONNECTED) && ($this->bitmap & self::MASK_LOGIN)) { + if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) { switch (ord($payload[0])) { case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4 + if (strlen($payload) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($payload, 4))); $this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . Strings::shift($payload, $length); @@ -3049,8 +3187,14 @@ class SSH2 break; case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 Strings::shift($payload, 1); + if (strlen($payload) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($payload, 4))); $data = Strings::shift($payload, $length); + if (strlen($payload) < 4) { + return false; + } extract(unpack('Nserver_channel', Strings::shift($payload, 4))); switch ($data) { case 'auth-agent': @@ -3058,6 +3202,9 @@ class SSH2 if (isset($this->agent)) { $new_channel = self::CHANNEL_AGENT_FORWARD; + if (strlen($payload) < 8) { + return false; + } extract(unpack('Nremote_window_size', Strings::shift($payload, 4))); extract(unpack('Nremote_maximum_packet_size', Strings::shift($payload, 4))); @@ -3103,8 +3250,12 @@ class SSH2 break; case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST: Strings::shift($payload, 1); + if (strlen($payload) < 8) { + return false; + } extract(unpack('Nchannel', Strings::shift($payload, 4))); extract(unpack('Nwindow_size', Strings::shift($payload, 4))); + $this->window_size_client_to_server[$channel]+= $window_size; $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->get_binary_packet(); @@ -3168,6 +3319,10 @@ class SSH2 */ public function disablePTY() { + if ($this->in_request_pty_exec) { + $this->_close_channel(self::CHANNEL_EXEC); + $this->in_request_pty_exec = false; + } $this->request_pty = false; } @@ -3233,8 +3388,14 @@ class SSH2 return ''; } + if (!strlen($response)) { + return false; + } extract(unpack('Ctype', Strings::shift($response, 1))); + if (strlen($response) < 4) { + return false; + } if ($type == NET_SSH2_MSG_CHANNEL_OPEN) { extract(unpack('Nlength', Strings::shift($response, 4))); } else { @@ -3258,15 +3419,26 @@ class SSH2 case NET_SSH2_MSG_CHANNEL_OPEN: switch ($type) { case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + if (strlen($response) < 4) { + return false; + } extract(unpack('Nserver_channel', Strings::shift($response, 4))); $this->server_channels[$channel] = $server_channel; + if (strlen($response) < 4) { + return false; + } extract(unpack('Nwindow_size', Strings::shift($response, 4))); + if ($window_size < 0) { $window_size&= 0x7FFFFFFF; $window_size+= 0x80000000; } $this->window_size_client_to_server[$channel] = $window_size; + if (strlen($response) < 4) { + return false; + } $temp = unpack('Npacket_size_client_to_server', Strings::shift($response, 4)); + $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server']; $result = $client_channel == $channel ? true : $this->get_channel_packet($client_channel, $skip_extended); $this->on_channel_open(); @@ -3305,6 +3477,9 @@ class SSH2 $this->send_channel_packet($channel, chr(0)); } */ + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $data = Strings::shift($response, $length); @@ -3331,8 +3506,12 @@ class SSH2 } */ // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR + if (strlen($response) < 8) { + return false; + } extract(unpack('Ndata_type_code/Nlength', Strings::shift($response, 8))); $data = Strings::shift($response, $length); + $this->stdErrorLog.= $data; if ($skip_extended || $this->quiet_mode) { break; @@ -3346,13 +3525,22 @@ class SSH2 $this->channel_buffers[$channel][] = $data; break; case NET_SSH2_MSG_CHANNEL_REQUEST: + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $value = Strings::shift($response, $length); switch ($value) { case 'exit-signal': Strings::shift($response, 1); + if (strlen($response) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($response, 4))); $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . Strings::shift($response, $length); + if (strlen($response) < 4) { + return false; + } Strings::shift($response, 1); extract(unpack('Nlength', Strings::shift($response, 4))); if ($length) { @@ -3366,7 +3554,11 @@ class SSH2 break; case 'exit-status': + if (strlen($response) < 5) { + return false; + } extract(unpack('Cfalse/Nexit_status', Strings::shift($response, 5))); + $this->exit_status = $exit_status; // "The client MAY ignore these messages." @@ -3978,6 +4170,9 @@ class SSH2 $signature = $this->signature; $server_public_host_key = $this->server_public_host_key; + if (strlen($server_public_host_key) < 4) { + return false; + } extract(unpack('Nlength', Strings::shift($server_public_host_key, 4))); Strings::shift($server_public_host_key, $length); @@ -3993,15 +4188,27 @@ class SSH2 case 'ssh-dss': $zero = new BigInteger(); + if (strlen($server_public_host_key) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $p = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); + if (strlen($server_public_host_key) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $q = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); + if (strlen($server_public_host_key) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $g = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); + if (strlen($server_public_host_key) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $y = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); @@ -4048,15 +4255,24 @@ class SSH2 break; case 'ssh-rsa': + if (strlen($server_public_host_key) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $e = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); + if (strlen($server_public_host_key) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $rawN = Strings::shift($server_public_host_key, $temp['length']); $n = new BigInteger($rawN, -256); $nLength = strlen(ltrim($rawN, "\0")); /* + if (strlen($signature) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($signature, 4)); $signature = Strings::shift($signature, $temp['length']); @@ -4069,6 +4285,9 @@ class SSH2 } */ + if (strlen($signature) < 4) { + return false; + } $temp = unpack('Nlength', Strings::shift($signature, 4)); $s = new BigInteger(Strings::shift($signature, $temp['length']), 256);