From fd83f04599c730bea73b7f30a599fd87c7e1605c Mon Sep 17 00:00:00 2001 From: Ghe Rivero Date: Sat, 5 May 2012 12:03:24 +0200 Subject: [PATCH] Fixed CVE_2012-2144. Closes: #671604 Rewritten-From: aad1f7ced2f6b5a40754f0d95a0d166e9e44403d --- xenial/debian/changelog | 6 + xenial/debian/patches/CVE_2012-2144 | 176 ++++++++++++++++++++++++++++ xenial/debian/patches/series | 1 + 3 files changed, 183 insertions(+) create mode 100644 xenial/debian/patches/CVE_2012-2144 diff --git a/xenial/debian/changelog b/xenial/debian/changelog index 5d6001c..5e0f6bf 100644 --- a/xenial/debian/changelog +++ b/xenial/debian/changelog @@ -1,3 +1,9 @@ +horizon (2012.1-4) unstable; urgency=low + + * Fixed CVE_2012-2144. Closes: #671604 + + -- Ghe Rivero Sat, 05 May 2012 12:02:08 +0200 + horizon (2012.1-3) unstable; urgency=low * Fixed CVE_2012-2094 diff --git a/xenial/debian/patches/CVE_2012-2144 b/xenial/debian/patches/CVE_2012-2144 new file mode 100644 index 0000000..0e048d9 --- /dev/null +++ b/xenial/debian/patches/CVE_2012-2144 @@ -0,0 +1,176 @@ +From abc532fa90eac1cc970423339347e318aa8d1b1a Mon Sep 17 00:00:00 2001 +From: Paul McMillan +Date: Fri, 4 May 2012 16:30:31 -0700 +Subject: [PATCH] Fixes lp978896 -- Session fixation security fix + +Rotates session tokens on logout, and properly clears sessions +to prevent data leakage. + +Change-Id: Id11054e852b8c8a386756e9de980cb5eff64f228 +--- + horizon/exceptions.py | 2 +- + horizon/middleware.py | 9 ++++++++ + horizon/tests/auth_tests.py | 52 +++++++++++++++++++++++++++++++++++++++++++ + horizon/users.py | 2 +- + horizon/views/auth.py | 2 +- + horizon/views/auth_forms.py | 12 +++++++++- + 6 files changed, 75 insertions(+), 4 deletions(-) + +diff --git a/horizon/exceptions.py b/horizon/exceptions.py +index c5f2450..cf460e3 100644 +--- a/horizon/exceptions.py ++++ b/horizon/exceptions.py +@@ -203,7 +203,7 @@ def handle(request, message=None, redirect=None, ignore=False, escalate=False): + if issubclass(exc_type, UNAUTHORIZED): + if ignore: + return NotAuthorized +- request.session.clear() ++ request.user_logout() + if not handled: + LOG.debug("Unauthorized: %s" % exc_value) + # We get some pretty useless error messages back from +diff --git a/horizon/middleware.py b/horizon/middleware.py +index f20c1f0..f141ff3 100644 +--- a/horizon/middleware.py ++++ b/horizon/middleware.py +@@ -49,6 +49,15 @@ class HorizonMiddleware(object): + + Adds a :class:`~horizon.users.User` object to ``request.user``. + """ ++ # A quick and dirty way to log users out ++ def user_logout(request): ++ if hasattr(request, '_cached_user'): ++ del request._cached_user ++ # Use flush instead of clear, so we rotate session keys in ++ # addition to clearing all the session data ++ request.session.flush() ++ request.__class__.user_logout = user_logout ++ + request.__class__.user = users.LazyUser() + request.horizon = {'dashboard': None, 'panel': None} + +diff --git a/horizon/tests/auth_tests.py b/horizon/tests/auth_tests.py +index ba16477..9f295d0 100644 +--- a/horizon/tests/auth_tests.py ++++ b/horizon/tests/auth_tests.py +@@ -18,6 +18,8 @@ + # License for the specific language governing permissions and limitations + # under the License. + ++import time ++ + from django import http + from django.core.urlresolvers import reverse + from keystoneclient import exceptions as keystone_exceptions +@@ -220,3 +222,53 @@ class AuthViewTests(test.TestCase): + + self.assertRedirectsNoFollow(res, reverse('splash')) + self.assertNotIn(KEY, self.client.session) ++ ++ def test_session_fixation(self): ++ session_ids = [] ++ form_data = {'method': 'Login', ++ 'region': 'http://localhost:5000/v2.0', ++ 'password': self.user.password, ++ 'username': self.user.name} ++ ++ self.mox.StubOutWithMock(api, 'token_create') ++ self.mox.StubOutWithMock(api, 'tenant_list_for_token') ++ self.mox.StubOutWithMock(api, 'token_create_scoped') ++ ++ aToken = self.tokens.unscoped_token ++ bToken = self.tokens.scoped_token ++ ++ api.token_create(IsA(http.HttpRequest), "", self.user.name, ++ self.user.password).AndReturn(aToken) ++ api.tenant_list_for_token(IsA(http.HttpRequest), ++ aToken.id).AndReturn([self.tenants.first()]) ++ api.token_create_scoped(IsA(http.HttpRequest), ++ self.tenant.id, ++ aToken.id).AndReturn(bToken) ++ ++ api.token_create(IsA(http.HttpRequest), "", self.user.name, ++ self.user.password).AndReturn(aToken) ++ api.tenant_list_for_token(IsA(http.HttpRequest), ++ aToken.id).AndReturn([self.tenants.first()]) ++ api.token_create_scoped(IsA(http.HttpRequest), ++ self.tenant.id, ++ aToken.id).AndReturn(bToken) ++ self.mox.ReplayAll() ++ ++ res = self.client.get(reverse('horizon:auth_login')) ++ self.assertEqual(res.cookies.get('sessionid'), None) ++ res = self.client.post(reverse('horizon:auth_login'), form_data) ++ session_ids.append(res.cookies['sessionid'].value) ++ ++ self.assertEquals(self.client.session['user_name'], ++ self.user.name) ++ self.client.session['foobar'] = 'MY TEST VALUE' ++ res = self.client.get(reverse('horizon:auth_logout')) ++ session_ids.append(res.cookies['sessionid'].value) ++ self.assertEqual(len(self.client.session.items()), 0) ++ # Sleep for 1 second so the session values are different if ++ # using the signed_cookies backend. ++ time.sleep(1) ++ res = self.client.post(reverse('horizon:auth_login'), form_data) ++ session_ids.append(res.cookies['sessionid'].value) ++ # Make sure all 3 session id values are different ++ self.assertEqual(len(session_ids), len(set(session_ids))) +diff --git a/horizon/users.py b/horizon/users.py +index f5dcde8..6e6be8e 100644 +--- a/horizon/users.py ++++ b/horizon/users.py +@@ -59,7 +59,7 @@ def get_user_from_request(request): + # If any of those keys are missing from the session it is + # overwhelmingly likely that we're dealing with an outdated session. + LOG.exception("Error while creating User from session.") +- request.session.clear() ++ request.user_logout() + raise exceptions.NotAuthorized(_("Your session has expired. " + "Please log in again.")) + +diff --git a/horizon/views/auth.py b/horizon/views/auth.py +index b48f24a..5120eed 100644 +--- a/horizon/views/auth.py ++++ b/horizon/views/auth.py +@@ -96,6 +96,6 @@ def switch_tenants(request, tenant_id): + + def logout(request): + """ Clears the session and logs the current user out. """ +- request.session.clear() ++ request.user_logout() + # FIXME(gabriel): we don't ship a view named splash + return shortcuts.redirect('splash') +diff --git a/horizon/views/auth_forms.py b/horizon/views/auth_forms.py +index 2874486..2ebecfc 100644 +--- a/horizon/views/auth_forms.py ++++ b/horizon/views/auth_forms.py +@@ -77,6 +77,16 @@ class Login(forms.SelfHandlingForm): + self.fields['region'].widget = forms.widgets.HiddenInput() + + def handle(self, request, data): ++ if 'user_name' in request.session: ++ if request.session['user_name'] != data['username']: ++ # To avoid reusing another user's session, create a ++ # new, empty session if the existing session ++ # corresponds to a different authenticated user. ++ request.session.flush() ++ # Always cycle the session key when viewing the login form to ++ # prevent session fixation ++ request.session.cycle_key() ++ + # For now we'll allow fallback to OPENSTACK_KEYSTONE_URL if the + # form post doesn't include a region. + endpoint = data.get('region', None) or settings.OPENSTACK_KEYSTONE_URL +@@ -116,7 +126,7 @@ class Login(forms.SelfHandlingForm): + # If we get here we don't want to show a stack trace to the + # user. However, if we fail here, there may be bad session + # data that's been cached already. +- request.session.clear() ++ request.user_logout() + exceptions.handle(request, + message=_("An error occurred authenticating." + " Please try again later."), +-- +1.7.10 + diff --git a/xenial/debian/patches/series b/xenial/debian/patches/series index ee073ee..743b66f 100644 --- a/xenial/debian/patches/series +++ b/xenial/debian/patches/series @@ -1 +1,2 @@ CVE_2012-2094 +CVE_2012-2144 -- 2.45.2