diff --git a/openedx/core/djangoapps/user_authn/views/logout.py b/openedx/core/djangoapps/user_authn/views/logout.py index ef8e3bf59a..4d88433a4f 100644 --- a/openedx/core/djangoapps/user_authn/views/logout.py +++ b/openedx/core/djangoapps/user_authn/views/logout.py @@ -5,6 +5,7 @@ import re import urllib.parse as parse # pylint: disable=import-error from urllib.parse import parse_qs, urlsplit, urlunsplit # pylint: disable=import-error +import bleach from django.conf import settings from django.contrib.auth import logout from django.utils.http import urlencode @@ -58,7 +59,7 @@ class LogoutView(TemplateView): # >> /courses/course-v1:ARTS+D1+2018_T/course/ # to handle this scenario we need to encode our URL using quote_plus and then unquote it again. if target_url: - target_url = parse.unquote(parse.quote_plus(target_url)) + target_url = bleach.clean(parse.unquote(parse.quote_plus(target_url))) use_target_url = target_url and is_safe_login_or_logout_redirect( redirect_to=target_url, diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_logout.py b/openedx/core/djangoapps/user_authn/views/tests/test_logout.py index 6e906ca53f..2a458cb4fc 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_logout.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_logout.py @@ -7,6 +7,7 @@ import unittest import urllib from unittest import mock import ddt +import bleach from django.conf import settings from django.test import TestCase from django.test.utils import override_settings @@ -193,3 +194,21 @@ class LogoutTests(TestCase): 'show_tpa_logout_link': True, } self.assertDictContainsSubset(expected, response.context_data) + + @ddt.data( + ('%22%3E%3Cscript%3Ealert(%27xss%27)%3C/script%3E', 'edx.org'), + ) + @ddt.unpack + def test_logout_redirect_failure_with_xss_vulnerability(self, redirect_url, host): + """ + Verify that it will block the XSS attack on edX’s LMS logout page + """ + url = '{logout_path}?redirect_url={redirect_url}'.format( + logout_path=reverse('logout'), + redirect_url=redirect_url + ) + response = self.client.get(url, HTTP_HOST=host) + expected = { + 'target': bleach.clean(urllib.parse.unquote(redirect_url)), + } + self.assertDictContainsSubset(expected, response.context_data)