Skip to content

Commit

Permalink
SSH2: make it so phpseclib initiates key re-exchange after 1GB
Browse files Browse the repository at this point in the history
  • Loading branch information
terrafrost committed Nov 22, 2024
1 parent 2b3d219 commit 7f31a1e
Showing 1 changed file with 118 additions and 26 deletions.
144 changes: 118 additions & 26 deletions phpseclib/Net/SSH2.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@
* Dumps the content real-time to a file
*/
define('NET_SSH2_LOG_REALTIME_FILE', 4);
/**
* Dumps the message numbers real-time
*/
define('NET_SSH2_LOG_REALTIME_SIMPLE', 5);
/**
* Make sure that the log never gets larger than this
*/
Expand Down Expand Up @@ -1107,6 +1111,46 @@ class Net_SSH2
*/
var $extra_packets;

/**
* Bytes Transferred Since Last Key Exchange
*
* Includes outbound and inbound totals
*
* @var int
* @access private
*/
var $bytesTransferredSinceLastKEX = 0;

/**
* After how many transferred byte should phpseclib initiate a key re-exchange?
*
* @var int
* @access private
*/
var $doKeyReexchangeAfterXBytes = 1024 * 1024 * 1024;

/**
* Has a key re-exchange been initialized?
*
* @var bool
* @access private
*/
var $keyExchangeInProgress = false;

/**
* KEX Buffer
*
* If we're in the middle of a key exchange we want to buffer any additional packets we get until
* the key exchange is over
*
* @see self::_get_binary_packet()
* @see self::_key_exchange()
* @see self::exec()
* @var array
* @access private
*/
var $kex_buffer = array();

/**
* Default Constructor.
*
Expand Down Expand Up @@ -1492,8 +1536,13 @@ function _generate_identifier()
*/
function _key_exchange($kexinit_payload_server = false)
{
$this->bytesTransferredSinceLastKEX = 0;

$preferred = $this->preferred;
$send_kex = true;
// for the initial key exchange $send_kex is true (no key re-exchange has been started)
// for phpseclib initiated key exchanges $send_kex is false
$send_kex = !$this->keyExchangeInProgress;
$this->keyExchangeInProgress = true;

$kex_algorithms = isset($preferred['kex']) ?
$preferred['kex'] :
Expand Down Expand Up @@ -1579,22 +1628,29 @@ function _key_exchange($kexinit_payload_server = false)
0
);

if ($kexinit_payload_server === false) {
if ($kexinit_payload_server === false && $send_kex) {
if (!$this->_send_binary_packet($kexinit_payload_client)) {
return false;
}

$this->extra_packets = 0;
$kexinit_payload_server = $this->_get_binary_packet();
if ($kexinit_payload_server === false) {
$this->bitmap = 0;
user_error('Connection closed by server');
return false;
}
while (true) {
$kexinit_payload_server = $this->_get_binary_packet();
if ($kexinit_payload_server === false) {
$this->bitmap = 0;
user_error('Connection closed by server');
return false;
}

if (strlen($kexinit_payload_server)) {
switch (ord($kexinit_payload_server[0])) {
case NET_SSH2_MSG_KEXINIT:
break 2;
case NET_SSH2_MSG_DISCONNECT:
return $this->_handleDisconnect($kexinit_payload_server);
}
}

if (!strlen($kexinit_payload_server) || ord($kexinit_payload_server[0]) != NET_SSH2_MSG_KEXINIT) {
user_error('Expected SSH_MSG_KEXINIT');
return false;
$this->kex_buffer[] = $kexinit_payload_server;
}

$send_kex = false;
Expand All @@ -1610,7 +1666,7 @@ function _key_exchange($kexinit_payload_server = false)
$temp = unpack('Nlength', $this->_string_shift($response, 4));
$this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length']));
if (in_array('[email protected]', $this->kex_algorithms)) {
if ($this->session_id === false && $this->extra_packets) {
if ($this->session_id === false && count($this->kex_buffer)) {
user_error('Possible Terrapin Attack detected');
return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
}
Expand Down Expand Up @@ -2001,6 +2057,8 @@ function _key_exchange($kexinit_payload_server = false)
return false;
}

$this->keyExchangeInProgress = false;

if (in_array('[email protected]', $this->kex_algorithms)) {
$this->get_seq_no = $this->send_seq_no = 0;
}
Expand Down Expand Up @@ -3606,6 +3664,10 @@ function _reset_connection($reason)
*/
function _get_binary_packet($skip_channel_filter = false)
{
if (!$this->keyExchangeInProgress && count($this->kex_buffer)) {
return $this->_filter(array_shift($this->kex_buffer), $skip_channel_filter);
}

if ($skip_channel_filter) {
$read = array($this->fsock);
$write = $except = null;
Expand Down Expand Up @@ -3687,9 +3749,11 @@ function _get_binary_packet($skip_channel_filter = false)

$remaining_length = $packet_length + 4 - $this->decrypt_block_size;

$this->bytesTransferredSinceLastKEX+= $packet_length + $padding_length + 5;

// quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
// "implementations SHOULD check that the packet length is reasonable"
// PuTTY uses 0x9000 as the actual max packet size and so to shall we
// PuTTY uses 0x9000 as the actual max packet size and so, too, 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->decryptName) && !($this->bitmap & NET_SSH2_MASK_LOGIN)) {
$this->bad_key_size_fix = true;
Expand Down Expand Up @@ -3779,7 +3843,34 @@ function _get_binary_packet($skip_channel_filter = false)
$this->last_packet = $current;
}

return $this->_filter($payload, $skip_channel_filter);
if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) {
$this->_key_exchange();
}

// don't filter if we're in the middle of a key exchange (since _filter might send out packets)
return $this->keyExchangeInProgress ? $payload : $this->_filter($payload, $skip_channel_filter);
}

/**
* Handle Disconnect
*
* Because some binary packets need to be ignored...
*
* @see self::_filter()
* @see self::_key_exchange
* @return boolean
* @access private
*/
function _handleDisconnect($payload)
{
$this->_string_shift($payload, 1);
if (strlen($payload) < 8) {
return false;
}
extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8)));
$this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . $this->_string_shift($payload, $length);
$this->bitmap = 0;
return false;
}

/**
Expand All @@ -3795,20 +3886,11 @@ function _filter($payload, $skip_channel_filter)
{
switch (ord($payload[0])) {
case NET_SSH2_MSG_DISCONNECT:
$this->_string_shift($payload, 1);
if (strlen($payload) < 8) {
return false;
}
extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8)));
$this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . $this->_string_shift($payload, $length);
$this->bitmap = 0;
return false;
return $this->_handleDisconnect($payload);
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;
Expand All @@ -3820,7 +3902,7 @@ function _filter($payload, $skip_channel_filter)
case NET_SSH2_MSG_UNIMPLEMENTED:
return false;
case NET_SSH2_MSG_KEXINIT:
// this is here for key re-exchanges after the initial key exchange
// this is here for server initiated key re-exchanges after the initial key exchange
if ($this->session_id !== false) {
$this->send_kex_first = false;
if (!$this->_key_exchange($payload)) {
Expand Down Expand Up @@ -4386,6 +4468,8 @@ function _send_binary_packet($data, $logged = null)

$packet.= $hmac;

$this->bytesTransferredSinceLastKEX+= strlen($packet);

$start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
$result = strlen($packet) == @fputs($this->fsock, $packet);
$stop = strtok(microtime(), ' ') + strtok('');
Expand All @@ -4399,6 +4483,10 @@ function _send_binary_packet($data, $logged = null)
$this->last_packet = $current;
}

if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) {
$this->_key_exchange();
}

return $result;
}

Expand Down Expand Up @@ -4476,6 +4564,10 @@ function _append_log($message_number, $message)
$this->realtime_log_wrap = true;
}
fputs($this->realtime_log_file, $entry);
break;
case NET_SSH2_LOG_REALTIME_SIMPLE:
echo $message_number;
echo PHP_SAPI == 'cli' ? "\r\n" : '<br>';
}
}

Expand Down

0 comments on commit 7f31a1e

Please sign in to comment.