From cecabb1feaad05ad9990eb27cf7ecae102341e21 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Mon, 27 Mar 2023 17:46:46 -0500 Subject: [PATCH 1/2] SSH/SFTP: create new openChannel() method to eliminate dupe code --- phpseclib/Net/SFTP.php | 15 +----- phpseclib/Net/SSH2.php | 107 ++++++++++++++--------------------------- 2 files changed, 37 insertions(+), 85 deletions(-) diff --git a/phpseclib/Net/SFTP.php b/phpseclib/Net/SFTP.php index a157b665..7015d904 100644 --- a/phpseclib/Net/SFTP.php +++ b/phpseclib/Net/SFTP.php @@ -547,20 +547,7 @@ class SFTP extends SSH2 { $this->window_size_server_to_client[self::CHANNEL] = $this->window_size; - $packet = Strings::packSSH2( - 'CsN3', - NET_SSH2_MSG_CHANNEL_OPEN, - 'session', - self::CHANNEL, - $this->window_size, - 0x4000 - ); - - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN; - - $response = $this->get_channel_packet(self::CHANNEL, true); + $response = $this->openChannel(self::CHANNEL, true); if ($response === true && $this->isTimeout()) { return false; } diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 19d52576..b554759b 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -2729,28 +2729,7 @@ class SSH2 throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.'); } - // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to - // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, - // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway. - // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info - $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size; - // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy - // uses 0x4000, that's what will be used here, as well. - $packet_size = 0x4000; - - $packet = Strings::packSSH2( - 'CsN3', - NET_SSH2_MSG_CHANNEL_OPEN, - 'session', - self::CHANNEL_EXEC, - $this->window_size_server_to_client[self::CHANNEL_EXEC], - $packet_size - ); - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN; - - $this->get_channel_packet(self::CHANNEL_EXEC); + $this->openChannel(self::CHANNEL_EXEC); if ($this->request_pty === true) { $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); @@ -2830,6 +2809,38 @@ class SSH2 } } + /** + * Opens a channel + * + * @param string $channel + */ + protected function openChannel($channel, $skip_extended = false) + { + // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to + // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, + // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway. + // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info + $this->window_size_server_to_client[$channel] = $this->window_size; + // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy + // uses 0x4000, that's what will be used here, as well. + $packet_size = 0x4000; + + $packet = Strings::packSSH2( + 'CsN3', + NET_SSH2_MSG_CHANNEL_OPEN, + 'session', + $channel, + $this->window_size_server_to_client[$channel], + $packet_size + ); + + $this->send_binary_packet($packet); + + $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_OPEN; + + return $this->get_channel_packet($channel, $skip_extended); + } + /** * Creates an interactive shell * @@ -2854,23 +2865,7 @@ class SSH2 throw new InsufficientSetupException('Operation disallowed prior to login()'); } - $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size; - $packet_size = 0x4000; - - $packet = Strings::packSSH2( - 'CsN3', - NET_SSH2_MSG_CHANNEL_OPEN, - 'session', - self::CHANNEL_SHELL, - $this->window_size_server_to_client[self::CHANNEL_SHELL], - $packet_size - ); - - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN; - - $this->get_channel_packet(self::CHANNEL_SHELL); + $this->openChannel(self::CHANNEL_SHELL); $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); $packet = Strings::packSSH2( @@ -3110,22 +3105,7 @@ class SSH2 */ public function startSubsystem($subsystem) { - $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size; - - $packet = Strings::packSSH2( - 'CsN3', - NET_SSH2_MSG_CHANNEL_OPEN, - 'session', - self::CHANNEL_SUBSYSTEM, - $this->window_size, - 0x4000 - ); - - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN; - - $this->get_channel_packet(self::CHANNEL_SUBSYSTEM); + $this->openChannel(self::CHANNEL_SUBSYSTEM); $packet = Strings::packSSH2( 'CNsCs', @@ -3303,23 +3283,8 @@ class SSH2 return false; } - $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size; - $packet_size = 0x4000; - $packet = Strings::packSSH2( - 'CsN3', - NET_SSH2_MSG_CHANNEL_OPEN, - 'session', - self::CHANNEL_KEEP_ALIVE, - $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE], - $packet_size - ); - try { - $this->send_binary_packet($packet); - - $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN; - - $response = $this->get_channel_packet(self::CHANNEL_KEEP_ALIVE); + $this->openChannel(self::CHANNEL_KEEP_ALIVE); } catch (\RuntimeException $e) { return $this->reconnect(); } From 5fb084b04ca8a95b5c4b14df20ea168f0f7f9e20 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Wed, 29 Mar 2023 03:52:00 -0500 Subject: [PATCH 2/2] SSH2: if the server doesn't support multiple channels error out --- phpseclib/Net/SSH2.php | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index b554759b..42f1f4e4 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -1087,6 +1087,21 @@ class SSH2 */ private $smartMFA = true; + /** + * How many channels are currently opened + * + * @var int + */ + private $channelCount = 0; + + /** + * Does the server support multiple channels? If not then error out + * when multiple channels are attempted to be opened + * + * @var bool + */ + private $errorOnMultipleChannels; + /** * Default Constructor. * @@ -1384,6 +1399,18 @@ class SSH2 throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers"); } + // Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see + // https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info. + // https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses + // when consolekit was incorporated. + // https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the + // issues with how Ubuntu incorporated consolekit + $pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#'; + $match = preg_match($pattern, $this->server_identifier, $matches); + $match = $match && version_compare('5.8', $matches[1], '<='); + $match = $match && version_compare('6.9', $matches[1], '>='); + $this->errorOnMultipleChannels = $match; + if (!$this->send_id_string_first) { fputs($this->fsock, $this->identifier . "\r\n"); } @@ -2813,9 +2840,17 @@ class SSH2 * Opens a channel * * @param string $channel + * @param bool $skip_extended + * @return bool */ protected function openChannel($channel, $skip_extended = false) { + $this->channelCount++; + + if ($this->channelCount > 1 && $this->errorOnMultipleChannels) { + throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels"); + } + // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but, // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.