Skip to content

Commit

Permalink
Fix and changelog re #1283
Browse files Browse the repository at this point in the history
  • Loading branch information
bitprophet committed Sep 19, 2018
1 parent 852176d commit 56c96a6
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 5 deletions.
36 changes: 32 additions & 4 deletions paramiko/auth_handler.py
Expand Up @@ -676,17 +676,39 @@ def _handle_local_gss_failure(self, e):
self.auth_event.set()
return

_handler_table = {
# TODO: do the same to the other tables, in Transport.
# TODO 3.0: MAY make sense to make these tables into actual
# classes/instances that can be fed a mode bool or whatever. Or,
# alternately (both?) make the message types small classes or enums that
# embed this info within themselves (which could also then tidy up the
# current 'integer -> human readable short string' stuff in common.py).
# TODO: if we do that, also expose 'em publicly.

# Messages which should be handled _by_ servers (sent by clients)
_server_handler_table = {
MSG_SERVICE_REQUEST: _parse_service_request,
MSG_SERVICE_ACCEPT: _parse_service_accept,
MSG_USERAUTH_REQUEST: _parse_userauth_request,
MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
}

# Messages which should be handled _by_ clients (sent by servers)
_client_handler_table = {
MSG_SERVICE_ACCEPT: _parse_service_accept,
MSG_USERAUTH_SUCCESS: _parse_userauth_success,
MSG_USERAUTH_FAILURE: _parse_userauth_failure,
MSG_USERAUTH_BANNER: _parse_userauth_banner,
MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request,
MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response,
}

# NOTE: prior to the fix for #1283, this was a static dict instead of a
# property. Should be backwards compatible in most/all cases.
@property
def _handler_table(self):
if self.transport.server_mode:
return self._server_handler_table
else:
return self._client_handler_table


class GssapiWithMicAuthHandler(object):
"""A specialized Auth handler for gssapi-with-mic
Expand Down Expand Up @@ -782,9 +804,15 @@ def _parse_userauth_request(self, m):
self._restore_delegate_auth_handler()
return self._delegate._parse_userauth_request(m)

_handler_table = {
__handler_table = {
MSG_SERVICE_REQUEST: _parse_service_request,
MSG_USERAUTH_REQUEST: _parse_userauth_request,
MSG_USERAUTH_GSSAPI_TOKEN: _parse_userauth_gssapi_token,
MSG_USERAUTH_GSSAPI_MIC: _parse_userauth_gssapi_mic,
}

@property
def _handler_table(self):
# TODO: determine if we can cut this up like we did for the primary
# AuthHandler class.
return self.__handler_table
12 changes: 12 additions & 0 deletions sites/www/changelog.rst
Expand Up @@ -7,6 +7,18 @@ Changelog
This behavior probably didn't cause any outright errors, but it doesn't seem
to conform to the RFCs and could cause (non-infinite) feedback loops in some
scenarios (usually those involving Paramiko on both ends).
- :bug:`1283 (1.17+)` Fix exploit (CVE pending) in Paramiko's server mode
(**not** client mode) where hostile clients could trick the server into
thinking they were authenticated without actually submitting valid
authentication.

Specifically, steps have been taken to start separating client and server
related message types in the message handling tables within ``Transport`` and
``AuthHandler``; this work is not complete but enough has been performed to
close off this particular exploit (which was the only obvious such exploit
for this particular channel).

Thanks to Daniel Hoffman for the detailed report.
- :support:`1292 backported` Backport changes from :issue:`979` (added in
Paramiko 2.3) to Paramiko 2.0-2.2, using duck-typing to preserve backwards
compatibility. This allows these older versions to use newer Cryptography
Expand Down
51 changes: 50 additions & 1 deletion tests/test_transport.py
Expand Up @@ -42,6 +42,7 @@
ChannelException,
Packetizer,
Channel,
AuthHandler,
)
from paramiko import AUTH_FAILED, AUTH_SUCCESSFUL
from paramiko import OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
Expand All @@ -54,8 +55,11 @@
MAX_WINDOW_SIZE,
DEFAULT_WINDOW_SIZE,
DEFAULT_MAX_PACKET_SIZE,
MSG_NAMES,
MSG_UNIMPLEMENTED,
MSG_USERAUTH_SUCCESS,
)
from paramiko.py3compat import bytes
from paramiko.py3compat import bytes, byte_chr
from paramiko.message import Message

from .util import needs_builtin, _support, slow
Expand Down Expand Up @@ -1052,3 +1056,48 @@ def test_server_does_not_respond_to_MSG_UNIMPLEMENTED(self):

def test_client_does_not_respond_to_MSG_UNIMPLEMENTED(self):
self._send_unimplemented(server_is_sender=True)

def _send_client_message(self, message_type):
self.setup_test_server(connect_kwargs={})
self.ts._send_message = Mock()
# NOTE: this isn't 100% realistic (most of these message types would
# have actual other fields in 'em) but it suffices to test the level of
# message dispatch we're interested in here.
msg = Message()
# TODO: really not liking the whole cMSG_XXX vs MSG_XXX duality right
# now, esp since the former is almost always just byte_chr(the
# latter)...but since that's the case...
msg.add_byte(byte_chr(message_type))
self.tc._send_message(msg)
# No good way to actually wait for server action (see above tests re:
# MSG_UNIMPLEMENTED). Grump.
time.sleep(0.1)

def _expect_unimplemented(self):
# Ensure MSG_UNIMPLEMENTED was sent (implies it hit end of loop instead
# of truly handling the given message).
# NOTE: When bug present, this will actually be the first thing that
# fails (since in many cases actual message handling doesn't involve
# sending a message back right away).
assert self.ts._send_message.call_count == 1
reply = self.ts._send_message.call_args[0][0]
reply.rewind() # Because it's pre-send, not post-receive
assert reply.get_byte() == cMSG_UNIMPLEMENTED

def test_server_transports_reject_client_message_types(self):
# TODO: handle Transport's own tables too, not just its inner auth
# handler's table. See TODOs in auth_handler.py
for message_type in AuthHandler._client_handler_table:
self._send_client_message(message_type)
self._expect_unimplemented()
# Reset for rest of loop
self.tearDown()
self.setUp()

def test_server_rejects_client_MSG_USERAUTH_SUCCESS(self):
self._send_client_message(MSG_USERAUTH_SUCCESS)
# Sanity checks
assert not self.ts.authenticated
assert not self.ts.auth_handler.authenticated
# Real fix's behavior
self._expect_unimplemented()

0 comments on commit 56c96a6

Please sign in to comment.