Skip to content

Instantly share code, notes, and snippets.

@stevebeattie
Created March 16, 2018 22:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevebeattie/0eb190004e10ba0926ad8782f89676ad to your computer and use it in GitHub Desktop.
Save stevebeattie/0eb190004e10ba0926ad8782f89676ad to your computer and use it in GitHub Desktop.
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