CVE-2018-7750 (published 2018-03-13T18:29Z)

transport.py in the SSH server implementation of Paramiko before 1.17.6, 1.18.x before 1.18.5, 2.0.x before 2.0.8, 2.1.x before 2.1.5, 2.2.x before 2.2.3, 2.3.x before 2.3.2, and 2.4.x before 2.4.1 does not properly check whether authentication is completed before processing other requests, as demonstrated by channel-open. A customized SSH client can simply skip the authentication step.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-287
cwe_name: Improper Authentication
description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
extended_description: 
            
			
         
         

Fix information:
repo_url: https://github.com/paramiko/paramiko
commit_details: repo_url: https://github.com/paramiko/paramiko
author: Jeff Forcier
author_date: 2018-03-12 15:34:06-07:00
msg: Fixes CVE-2018-7750 / #1175

At least, insofar as the new tests pass...!
repository: repo_url: https://github.com/paramiko/paramiko
repo_name: paramiko/paramiko
description: The leading native Python SSHv2 protocol library.
repo_language: Python
owner: paramiko
file_changes: filename: common.py
change_type: ModificationType.MODIFY
diff: @@ -32,6 +32,7 @@ MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE = range(60, 62)
 MSG_USERAUTH_GSSAPI_RESPONSE, MSG_USERAUTH_GSSAPI_TOKEN = range(60, 62)
 MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, MSG_USERAUTH_GSSAPI_ERROR,\
     MSG_USERAUTH_GSSAPI_ERRTOK, MSG_USERAUTH_GSSAPI_MIC = range(63, 67)
+HIGHEST_USERAUTH_MESSAGE_ID = 79
 MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE = range(80, 83)
 MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \
     MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \

programming_language: Python
filename: transport.py
change_type: ModificationType.MODIFY
diff: @@ -49,7 +49,7 @@ from paramiko.common import (
     MSG_CHANNEL_FAILURE, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA,
     MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_REQUEST, MSG_CHANNEL_EOF,
     MSG_CHANNEL_CLOSE, MIN_WINDOW_SIZE, MIN_PACKET_SIZE, MAX_WINDOW_SIZE,
-    DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE,
+    DEFAULT_WINDOW_SIZE, DEFAULT_MAX_PACKET_SIZE, HIGHEST_USERAUTH_MESSAGE_ID,
 )
 from paramiko.compress import ZlibCompressor, ZlibDecompressor
 from paramiko.dsskey import DSSKey
@@ -1760,6 +1760,43 @@ class Transport(threading.Thread, ClosingContextManager):
             max_packet_size = self.default_max_packet_size
         return clamp_value(MIN_PACKET_SIZE, max_packet_size, MAX_WINDOW_SIZE)
 
+    def _ensure_authed(self, ptype, message):
+        """
+        Checks message type against current auth state.
+
+        If server mode, and auth has not succeeded, and the message is of a
+        post-auth type (channel open or global request) an appropriate error
+        response Message is crafted and returned to caller for sending.
+
+        Otherwise (client mode, authed, or pre-auth message) returns None.
+        """
+        if (
+            not self.server_mode
+            or ptype <= HIGHEST_USERAUTH_MESSAGE_ID
+            or self.is_authenticated()
+        ):
+            return None
+        # WELP. We must be dealing with someone trying to do non-auth things
+        # without being authed. Tell them off, based on message class.
+        reply = Message()
+        # Global requests have no details, just failure.
+        if ptype == MSG_GLOBAL_REQUEST:
+            reply.add_byte(cMSG_REQUEST_FAILURE)
+        # Channel opens let us reject w/ a specific type + message.
+        elif ptype == MSG_CHANNEL_OPEN:
+            kind = message.get_text()
+            chanid = message.get_int()
+            reply.add_byte(cMSG_CHANNEL_OPEN_FAILURE)
+            reply.add_int(chanid)
+            reply.add_int(OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED)
+            reply.add_string('')
+            reply.add_string('en')
+        # NOTE: Post-open channel messages do not need checking; the above will
+        # reject attemps to open channels, meaning that even if a malicious
+        # user tries to send a MSG_CHANNEL_REQUEST, it will simply fall under
+        # the logic that handles unknown channel IDs (as the channel list will
+        # be empty.)
+        return reply
 
     def run(self):
         # (use the exposed "run" method, because if we specify a thread target
@@ -1820,7 +1857,11 @@ class Transport(threading.Thread, ClosingContextManager):
                             continue
 
                     if ptype in self._handler_table:
-                        self._handler_table[ptype](self, m)
+                        error_msg = self._ensure_authed(ptype, m)
+                        if error_msg:
+                            self._send_message(error_msg)
+                        else:
+                            self._handler_table[ptype](self, m)
                     elif ptype in self._channel_handler_table:
                         chanid = m.get_int()
                         chan = self._channels.get(chanid)

programming_language: Python