paramiko fix for CVE-2018-7750 backport to paramiko 1.10
From e9dfd854bdaf8af15d7834f7502a0451d217bb8c Mon Sep 17 00:00:00 2001 | |
From: Jeff Forcier <jeff@bitprophet.org> | |
Date: Mon, 12 Mar 2018 15:34:06 -0700 | |
Subject: [PATCH] Fixes CVE-2018-7750 / #1175 | |
At least, insofar as the new tests pass...! | |
[Ubuntu note: backported patch to 1.10, including Message.get_text() | |
-> Message->get_string() conversion, cMSG_REQUEST_FAILURE -> | |
chr(MSG_REQUEST_FAILURE), and cMSG_CHANNEL_OPEN_FAILURE -> | |
chr(MSG_CHANNEL_OPEN_FAILURE). --sbeattie] | |
--- | |
paramiko/common.py | 1 + | |
paramiko/transport.py | 44 +++++++++++++++++++++++++++++++++++++++++++- | |
2 files changed, 44 insertions(+), 1 deletion(-) | |
Index: b/paramiko/common.py | |
=================================================================== | |
--- a/paramiko/common.py | |
+++ b/paramiko/common.py | |
@@ -27,6 +27,7 @@ MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILU | |
MSG_USERAUTH_BANNER = range(50, 54) | |
MSG_USERAUTH_PK_OK = 60 | |
MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE = range(60, 62) | |
+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, \ | |
Index: b/paramiko/transport.py | |
=================================================================== | |
--- a/paramiko/transport.py | |
+++ b/paramiko/transport.py | |
@@ -1525,6 +1525,44 @@ class Transport (threading.Thread): | |
finally: | |
self.lock.release() | |
+ 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(chr(MSG_REQUEST_FAILURE)) | |
+ # Channel opens let us reject w/ a specific type + message. | |
+ elif ptype == MSG_CHANNEL_OPEN: | |
+ kind = message.get_string() | |
+ chanid = message.get_int() | |
+ reply.add_byte(chr(MSG_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 | |
# of a private method, threading.Thread will keep a reference to it | |
@@ -1582,7 +1620,11 @@ class Transport (threading.Thread): | |
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment