From: terrafrost <terrafrost@php.net>
Date: Thu, 28 Dec 2023 06:27:57 -0600
Subject: SSH2: implement terrapin attack countermeasures

Origin: upstream, https://github.com/phpseclib/phpseclib/commit/c8e3ab9317abae80d7f58fd9acd9214b57572b32
Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2023-48795
---
 phpseclib/Net/SSH2.php | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php
index a24052a..93375df 100644
--- a/phpseclib/Net/SSH2.php
+++ b/phpseclib/Net/SSH2.php
@@ -1078,6 +1078,14 @@ class Net_SSH2
      */
     var $smartMFA = true;
 
+    /**
+     * Extra packets counter
+     *
+     * @var bool
+     * @access private
+     */
+    var $extra_packets;
+
     /**
      * Default Constructor.
      *
@@ -1491,7 +1499,7 @@ class Net_SSH2
             $preferred['client_to_server']['comp'] :
             $this->getSupportedCompressionAlgorithms();
 
-        $kex_algorithms = array_merge($kex_algorithms, array('ext-info-c'));
+        $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) {
@@ -1555,6 +1563,7 @@ class Net_SSH2
                 return false;
             }
 
+            $this->extra_packets = 0;
             $kexinit_payload_server = $this->_get_binary_packet();
             if ($kexinit_payload_server === false) {
                 $this->bitmap = 0;
@@ -1579,6 +1588,12 @@ class Net_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;
@@ -1965,6 +1980,10 @@ class Net_SSH2
             return false;
         }
 
+        if (in_array('kex-strict-s-v00@openssh.com', $this->kex_algorithms)) {
+            $this->get_seq_no = $this->send_seq_no = 0;
+        }
+
         $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt);
         $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt);
 
@@ -3745,9 +3764,11 @@ class Net_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;
@@ -3759,6 +3780,7 @@ class Net_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)) {
