From 4f77ee0e245eb3bdd398c25fc4862a6ca3f6bbf6 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Fri, 26 Feb 2016 12:03:32 +0100 Subject: [PATCH] do not pass strings contening null characters to Lasso, return 400 or ignore (fixes #8939) --- mellon/utils.py | 3 +++ mellon/views.py | 12 ++++++++---- tests/conftest.py | 9 +++++++++ tests/test_views.py | 4 ++++ testsettings.py | 8 ++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 tests/test_views.py diff --git a/mellon/utils.py b/mellon/utils.py index 34f1b41..7fa40be 100644 --- a/mellon/utils.py +++ b/mellon/utils.py @@ -202,3 +202,6 @@ def create_logout(request): logout.setSignatureHint(lasso.PROFILE_SIGNATURE_HINT_FORBID) logout.setSessionFromDump(session_dump) return logout + +def is_nonnull(s): + return not '\x00' in s diff --git a/mellon/views.py b/mellon/views.py index b9e83b3..2bc10a8 100644 --- a/mellon/views.py +++ b/mellon/views.py @@ -34,9 +34,12 @@ class LoginView(LogMixin, View): '''Assertion consumer''' if 'SAMLResponse' not in request.POST: return self.get(request, *args, **kwargs) + if not utils.is_nonnull(request.POST['SAMLResponse']): + return HttpResponseBadRequest('SAMLResponse contains a null character') login = utils.create_login(request) idp_message = None status_codes = [] + # prevent null characters in SAMLResponse try: login.processAuthnResponseMsg(request.POST['SAMLResponse']) login.acceptSso() @@ -63,7 +66,7 @@ class LoginView(LogMixin, View): return HttpResponseBadRequest('error processing the authentication ' 'response: %r' % e) else: - if 'RelayState' in request.POST: + if 'RelayState' in request.POST and utils.is_nonnull(request.POST['RelayState']): login.msgRelayState = request.POST['RelayState'] return self.sso_success(request, login) return self.sso_failure(request, login, idp_message, status_codes) @@ -204,7 +207,7 @@ class LoginView(LogMixin, View): return HttpResponseBadRequest('error processing the authentication ' 'response: %r' % e) else: - if 'RelayState' in request.GET: + if 'RelayState' in request.GET and utils.is_nonnull(request.GET['RelayState']): login.msgRelayState = request.GET['RelayState'] return self.sso_success(request, login) return self.sso_failure(request, login, idp_message, status_codes) @@ -238,7 +241,7 @@ class LoginView(LogMixin, View): req_authncontext = lasso.RequestedAuthnContext() authn_request.requestedAuthnContext = req_authncontext req_authncontext.authnContextClassRef = authn_classref - if next_url: + if next_url and utils.is_nonnull(next_url): login.msgRelayState = next_url login.buildAuthnRequestMsg() except lasso.Error, e: @@ -300,7 +303,8 @@ class LogoutView(LogMixin, View): else: self.log.error('unable to find lasso session dump') logout.initRequest(issuer, lasso.HTTP_METHOD_REDIRECT) - logout.msgRelayState = next_url + if utils.is_nonnull(next_url): + logout.msgRelayState = next_url logout.buildRequestMsg() except lasso.Error, e: self.log.error('unable to initiate a logout request %r', e) diff --git a/tests/conftest.py b/tests/conftest.py index ed76201..9c3c8a2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,13 @@ import pytest +import django_webtest + + +@pytest.fixture +def app(request): + wtm = django_webtest.WebTestMixin() + wtm._patch_settings() + request.addfinalizer(wtm._unpatch_settings) + return django_webtest.DjangoTestApp() @pytest.fixture diff --git a/tests/test_views.py b/tests/test_views.py new file mode 100644 index 0000000..7d8deef --- /dev/null +++ b/tests/test_views.py @@ -0,0 +1,4 @@ +from django.core.urlresolvers import reverse + +def test_null_character_on_samlresponse_post(app): + app.post(reverse('mellon_login'), {'SAMLResponse': '\x00'}, status=400) diff --git a/testsettings.py b/testsettings.py index 5f40084..bef6499 100644 --- a/testsettings.py +++ b/testsettings.py @@ -12,6 +12,14 @@ DATABASES = { } DEBUG = True SECRET_KEY='xx' +STATIC_URL = '/static/' INSTALLED_APPS = ('mellon', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions') MIDDLEWARE_CLASSES = global_settings.MIDDLEWARE_CLASSES +MIDDLEWARE_CLASSES += ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +) +AUTHENTICATION_BACKENDS = ( + 'mellon.backends.SAMLBackend', +) ROOT_URLCONF = 'mellon.urls'