Skip to content

Commit

Permalink
Merge branch '2.0' into 3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
terrafrost committed Nov 22, 2024
2 parents f7714a5 + 53645af commit 420de5c
Showing 1 changed file with 114 additions and 12 deletions.
126 changes: 114 additions & 12 deletions phpseclib/Net/SSH2.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ class SSH2
* Outputs the message numbers real-time
*/
const LOG_SIMPLE_REALTIME = 5;
/*
* Dumps the message numbers real-time
*/
const LOG_REALTIME_SIMPLE = 5;
/**
* Make sure that the log never gets larger than this
*
Expand Down Expand Up @@ -1125,6 +1129,44 @@ class SSH2
*/
private $extra_packets;

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

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

/**
* Has a key re-exchange been initialized?
*
* @var bool
* @access private
*/
private $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
*/
private $kex_buffer = [];

/**
* Default Constructor.
*
Expand Down Expand Up @@ -1535,8 +1577,14 @@ private function generate_identifier()
*/
private function key_exchange($kexinit_payload_server = false)
{
echo "KEY EXCHANGE CALLED\n";
$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 @@ -1616,11 +1664,22 @@ private function key_exchange($kexinit_payload_server = false)
0 // reserved for future extension
);

if ($kexinit_payload_server === false) {
if ($kexinit_payload_server === false && $send_kex) {
echo "sending " . strlen($kexinit_payload_client) . " bytes\n";
$this->send_binary_packet($kexinit_payload_client);

$this->extra_packets = 0;
$kexinit_payload_server = $this->get_binary_packet_or_close(NET_SSH2_MSG_KEXINIT);
while (true) {
$kexinit_payload_server = $this->get_binary_packet();
switch (ord($kexinit_payload_server[0])) {
case NET_SSH2_MSG_KEXINIT:
break 2;
case NET_SSH2_MSG_DISCONNECT:
return $this->handleDisconnect($kexinit_payload_server);
}

$this->kex_buffer[] = $kexinit_payload_server;
}

$send_kex = false;
}

Expand All @@ -1642,7 +1701,7 @@ private function key_exchange($kexinit_payload_server = false)
$first_kex_packet_follows
) = Strings::unpackSSH2('L10C', $response);
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)) {
throw new \UnexpectedValueException('Possible Terrapin Attack detected');
}
}
Expand Down Expand Up @@ -1881,6 +1940,8 @@ private function key_exchange($kexinit_payload_server = false)
$this->send_binary_packet($packet);
$response = $this->get_binary_packet_or_close(NET_SSH2_MSG_NEWKEYS);

$this->keyExchangeInProgress = false;

if (in_array('[email protected]', $this->kex_algorithms)) {
$this->get_seq_no = $this->send_seq_no = 0;
}
Expand Down Expand Up @@ -3529,6 +3590,9 @@ private function get_binary_packet()
if (!is_resource($this->fsock)) {
throw new \InvalidArgumentException('fsock is not a resource.');
}
if (!$this->keyExchangeInProgress && count($this->kex_buffer)) {
return $this->filter(array_shift($this->kex_buffer));
}
if ($this->binary_packet_buffer == null) {
// buffer the packet to permit continued reads across timeouts
$this->binary_packet_buffer = (object) [
Expand Down Expand Up @@ -3655,6 +3719,11 @@ private function get_binary_packet()
if ($padding_length > 0) {
Strings::pop($payload, $padding_length);
}

if (!$this->keyExchangeInProgress) {
$this->bytesTransferredSinceLastKEX += $packet->packet_length + $padding_length + 5;
}

if (empty($payload)) {
$this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
throw new ConnectionClosedException('Plaintext is too short');
Expand Down Expand Up @@ -3708,7 +3777,13 @@ private function get_binary_packet()
}
$this->last_packet = microtime(true);

return $this->filter($payload);
if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) {
echo "CALLING KEY EXCHANGE METHOD ({$this->bytesTransferredSinceLastKEX})\n";
$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);
}

/**
Expand Down Expand Up @@ -3773,6 +3848,25 @@ private function get_binary_packet_size(&$packet)
$packet->packet_length = $packet_length;
}

/**
* Handle Disconnect
*
* Because some binary packets need to be ignored...
*
* @see self::filter()
* @see self::key_exchange()
* @return boolean
* @access private
*/
private function handleDisconnect($payload)
{
Strings::shift($payload, 1);
list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
$this->errors[] = 'SSH_MSG_DISCONNECT: ' . self::$disconnect_reasons[$reason_code] . "\r\n$message";
$this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
throw new ConnectionClosedException('Connection closed by server');
}

/**
* Filter Binary Packets
*
Expand All @@ -3786,11 +3880,7 @@ private function filter($payload)
{
switch (ord($payload[0])) {
case NET_SSH2_MSG_DISCONNECT:
Strings::shift($payload, 1);
list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
$this->errors[] = 'SSH_MSG_DISCONNECT: ' . self::$disconnect_reasons[$reason_code] . "\r\n$message";
$this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
throw new ConnectionClosedException('Connection closed by server');
return $this->handleDisconnect($payload);
case NET_SSH2_MSG_IGNORE:
$this->extra_packets++;
$payload = $this->get_binary_packet();
Expand All @@ -3805,7 +3895,7 @@ private function filter($payload)
case NET_SSH2_MSG_UNIMPLEMENTED:
break; // return payload
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) {
if (!$this->key_exchange($payload)) {
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
Expand Down Expand Up @@ -4339,6 +4429,10 @@ protected function send_binary_packet($data, $logged = null)

$packet .= $this->encrypt && $this->encrypt->usesNonce() ? $this->encrypt->getTag() : $hmac;

if (!$this->keyExchangeInProgress) {
$this->bytesTransferredSinceLastKEX += strlen($packet);
}

$start = microtime(true);
$sent = @fputs($this->fsock, $packet);
$stop = microtime(true);
Expand All @@ -4359,6 +4453,10 @@ protected function send_binary_packet($data, $logged = null)
"Only $sent of " . strlen($packet) . " bytes were sent";
throw new \RuntimeException($message);
}

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

/**
Expand Down Expand Up @@ -4481,6 +4579,10 @@ protected function append_log_helper($constant, $message_number, $message, array
$realtime_log_wrap = true;
}
fputs($realtime_log_file, $entry);
break;
case self::LOG_REALTIME_SIMPLE:
echo $message_number;
echo PHP_SAPI == 'cli' ? "\r\n" : '<br>';
}
}

Expand Down

0 comments on commit 420de5c

Please sign in to comment.