From a33303b6beb5c316d7c18b23566b7666a4e307a4 Mon Sep 17 00:00:00 2001
From: Patrick Uiterwijk <puiterwijk@redhat.com>
Date: Nov 20 2016 18:32:53 +0000
Subject: [PATCH 1/2] Fix SAML2 multi-session vulnerability


This resolves an issue where Ipsilon can be requested to initiate logout
sessions for all currently open sessions, regardless of currently logged
in user.

Fixes: CVE-2016-8638
Signed-off-by: Patrick Uiterwijk <puiterwijk@redhat.com>
Reviewed-by: Howard Johnson <merlin@merlinthp.org>
Reviewed-by: Rob Crittenden <rcritten@redhat.com>

---

diff --git a/ipsilon/login/common.py b/ipsilon/login/common.py
index d0ef41a..e00d4db 100644
--- a/ipsilon/login/common.py
+++ b/ipsilon/login/common.py
@@ -374,10 +374,11 @@ class Logout(Page):
     def root(self, *args, **kwargs):
         us = UserSession()
 
-        for provider in self.handlers:
-            self.debug("Calling logout for provider %s" % provider)
-            obj = self.handlers[provider]
-            obj()
+        if us.user is not None:
+            for provider in self.handlers:
+                self.debug("Calling logout for provider %s" % provider)
+                obj = self.handlers[provider]
+                obj()
 
         us.logout(self.user)
         return self._template('logout.html', title='Logout')
diff --git a/ipsilon/providers/saml2/logout.py b/ipsilon/providers/saml2/logout.py
index b2ebe0b..e31ce91 100644
--- a/ipsilon/providers/saml2/logout.py
+++ b/ipsilon/providers/saml2/logout.py
@@ -278,7 +278,7 @@ class LogoutRequest(ProviderPageBase):
             lasso.SAML2_METADATA_BINDING_REDIRECT,
         ]
         (logout_mech, session) = saml_sessions.get_next_logout(
-            logout_mechs=logout_order)
+            logout_mechs=logout_order, user=us.user)
         while session:
             self.debug('Going to log out %s' % session.provider_id)
 
@@ -302,7 +302,7 @@ class LogoutRequest(ProviderPageBase):
                 )
                 saml_sessions.remove_session(session)
                 (logout_mech, session) = saml_sessions.get_next_logout(
-                    logout_mechs=logout_order)
+                    logout_mechs=logout_order, user=us.user)
                 continue
 
             try:
@@ -316,7 +316,7 @@ class LogoutRequest(ProviderPageBase):
             # log out
             self.debug('logging out provider id %s' % session.provider_id)
             indexes = saml_sessions.get_session_id_by_provider_id(
-                session.provider_id
+                session.provider_id, us.user
             )
             self.debug('Requesting logout for sessions %s' % (indexes,))
             req = logout.get_request()
@@ -350,7 +350,7 @@ class LogoutRequest(ProviderPageBase):
                     self.error('Provider does not support SOAP')
 
             (logout_mech, session) = saml_sessions.get_next_logout(
-                logout_mechs=logout_order)
+                logout_mechs=logout_order, user=us.user)
 
         # done while
 
@@ -358,7 +358,7 @@ class LogoutRequest(ProviderPageBase):
         # original request using the response we cached earlier.
 
         try:
-            session = saml_sessions.get_initial_logout()
+            session = saml_sessions.get_initial_logout(us.user)
         except ValueError:
             self.debug('SLO get_last_session() unable to find last session')
             raise cherrypy.HTTPError(400, 'Unable to determine logout state')
diff --git a/ipsilon/providers/saml2/sessions.py b/ipsilon/providers/saml2/sessions.py
index d3ed7e2..3de455e 100644
--- a/ipsilon/providers/saml2/sessions.py
+++ b/ipsilon/providers/saml2/sessions.py
@@ -113,7 +113,6 @@ class SAMLSessionFactory(Log):
     """
     def __init__(self, database_url):
         self._ss = SAML2SessionStore(database_url=database_url)
-        self.user = None
 
     def _data_to_samlsession(self, uuidval, data):
         """
@@ -143,8 +142,6 @@ class SAMLSessionFactory(Log):
         :param request_id: The request ID of the Logout
         :param supported_logout_mechs: A list of logout protocols supported
         """
-        self.user = user
-
         timeout = cherrypy_config['tools.sessions.timeout']
         t = datetime.timedelta(seconds=timeout * 60)
         expiration_time = datetime.datetime.now() + t
@@ -175,11 +172,11 @@ class SAMLSessionFactory(Log):
 
         return self._data_to_samlsession(uuidval, data)
 
-    def get_session_id_by_provider_id(self, provider_id):
+    def get_session_id_by_provider_id(self, provider_id, user):
         """
         Return a tuple of logged-in session IDs by provider_id
         """
-        candidates = self._ss.get_user_sessions(self.user)
+        candidates = self._ss.get_user_sessions(user)
 
         session_ids = []
         for c in candidates:
@@ -228,7 +225,7 @@ class SAMLSessionFactory(Log):
         self._ss.update_session(datum)
 
     def get_next_logout(self, peek=False,
-                        logout_mechs=None):
+                        logout_mechs=None, user=None):
         """
         Get the next session in the logged-in state and move
         it to the logging_out state.  Return the session that is
@@ -246,7 +243,7 @@ class SAMLSessionFactory(Log):
         Returns a tuple of (mechanism, session) or
         (None, None) if no more sessions in LOGGED_IN state.
         """
-        candidates = self._ss.get_user_sessions(self.user)
+        candidates = self._ss.get_user_sessions(user)
         if logout_mechs is None:
             logout_mechs = [SAML2_METADATA_BINDING_REDIRECT, ]
 
@@ -260,13 +257,13 @@ class SAMLSessionFactory(Log):
                     return (mech, samlsession)
         return (None, None)
 
-    def get_initial_logout(self):
+    def get_initial_logout(self, user):
         """
         Get the initial logout request.
 
         Raises ValueError if no sessions in INIT_LOGOUT state.
         """
-        candidates = self._ss.get_user_sessions(self.user)
+        candidates = self._ss.get_user_sessions(user)
 
         # FIXME: what does it mean if there are multiple in init? We
         #        just return the first one for now. How do we know
@@ -282,11 +279,11 @@ class SAMLSessionFactory(Log):
     def wipe_data(self):
         self._ss.wipe_data()
 
-    def dump(self):
+    def dump(self, user):
         """
         Dump all sessions to debug log
         """
-        candidates = self._ss.get_user_sessions(self.user)
+        candidates = self._ss.get_user_sessions(user)
 
         count = 0
         for c in candidates:
@@ -314,13 +311,13 @@ if __name__ == '__main__':
                                  SAML2_METADATA_BINDING_REDIRECT])
 
     # Test finding sessions by provider
-    ids = factory.get_session_id_by_provider_id(provider2)
+    ids = factory.get_session_id_by_provider_id(provider2, user='admin')
     assert(len(ids) == 1)
 
     sess3 = factory.add_session('_345678', provider2, "testuser",
                                 "<Login/>", '_3456',
                                 [SAML2_METADATA_BINDING_REDIRECT])
-    ids = factory.get_session_id_by_provider_id(provider2)
+    ids = factory.get_session_id_by_provider_id(provider2, user='testuser')
     assert(len(ids) == 2)
 
     # Test finding sessions by session ID
@@ -343,13 +340,13 @@ if __name__ == '__main__':
     test2 = factory.get_session_by_id('_789012')
     factory.start_logout(test2, initial=True)
 
-    (lmech, test3) = factory.get_next_logout()
+    (lmech, test3) = factory.get_next_logout(user='admin')
     assert(test3.session_id == '_345678')
 
-    test4 = factory.get_initial_logout()
+    test4 = factory.get_initial_logout(user='admin')
     assert(test4.session_id == '_789012')
 
     # Even though we've started logout, make sure we can still find
     # all sessions for a provider.
-    ids = factory.get_session_id_by_provider_id(provider2)
+    ids = factory.get_session_id_by_provider_id(provider2, user='admin')
     assert(len(ids) == 2)
diff --git a/ipsilon/providers/saml2idp.py b/ipsilon/providers/saml2idp.py
index bc0c548..507644e 100644
--- a/ipsilon/providers/saml2idp.py
+++ b/ipsilon/providers/saml2idp.py
@@ -410,7 +410,7 @@ Provides SAML 2.0 authentication infrastructure. """
         saml_sessions = self.sessionfactory
         # pylint: disable=unused-variable
         (mech, session) = saml_sessions.get_next_logout(
-            logout_mechs=[lasso.SAML2_METADATA_BINDING_REDIRECT])
+            logout_mechs=[lasso.SAML2_METADATA_BINDING_REDIRECT], user=us.user)
         if session is None:
             return
 

From 36f90af5ce25344e388110f204d7f309de3d5478 Mon Sep 17 00:00:00 2001
From: Patrick Uiterwijk <puiterwijk@redhat.com>
Date: Nov 20 2016 18:47:54 +0000
Subject: [PATCH 2/2] Release 2.0.2 for CVE-2016-8638


Signed-off-by: Patrick Uiterwijk <puiterwijk@redhat.com>

---

diff --git a/contrib/fedora/ipsilon.spec b/contrib/fedora/ipsilon.spec
index d67b065..fb5dc53 100644
--- a/contrib/fedora/ipsilon.spec
+++ b/contrib/fedora/ipsilon.spec
@@ -1,7 +1,7 @@
 # Bundling request for bootstrap/patternfly: https://fedorahosted.org/fpc/ticket/483
 
 Name:       ipsilon
-Version:    2.0.1
+Version:    2.0.2
 Release:    1%{?builddate}%{?gittag}%{?dist}
 Summary:    An Identity Provider Server
 
@@ -421,6 +421,9 @@ fi
 %{python2_sitelib}/ipsilon/info/infosssd.*
 
 %changelog
+* Mon Nov 21 2016 Patrick Uiterwijk <puiterwijk@redhat.com> - 2.0.2-1
+- Release 2.0.2 for CVE-2016-8638
+
 * Mon Oct 31 2016 Patrick Uiterwijk <puiterwijk@redhat.com> - 2.0.1-1
 - Enabling allow authz on upgrade
 
diff --git a/man/ipsilon-client-install.1 b/man/ipsilon-client-install.1
index 6ff1c94..f05fe70 100644
--- a/man/ipsilon-client-install.1
+++ b/man/ipsilon-client-install.1
@@ -1,6 +1,6 @@
 .\" Copyright (C) 2015 Ipsilon Project Contributors
 .\"
-.TH "ipsilon-client-install" "1" "2.0.1" "Ipsilon" "Ipsilon Manual Pages"
+.TH "ipsilon-client-install" "1" "2.0.2" "Ipsilon" "Ipsilon Manual Pages"
 .SH "NAME"
 ipsilon\-client\-install \- Configure an Ipsilon client
 .SH "SYNOPSIS"
diff --git a/man/ipsilon-server-install.1 b/man/ipsilon-server-install.1
index eb9365e..96dfb3c 100644
--- a/man/ipsilon-server-install.1
+++ b/man/ipsilon-server-install.1
@@ -1,6 +1,6 @@
 .\" Copyright (C) 2015 Ipsilon Project Contributors
 .\"
-.TH "ipsilon-server-install" "1" "2.0.1" "Ipsilon" "Ipsilon Manual Pages"
+.TH "ipsilon-server-install" "1" "2.0.2" "Ipsilon" "Ipsilon Manual Pages"
 .SH "NAME"
 ipsilon\-server\-install \- Configure an Ipsilon Identity Provider instance
 .SH "SYNOPSIS"
diff --git a/man/ipsilon.7 b/man/ipsilon.7
index bdd808a..eca6f83 100644
--- a/man/ipsilon.7
+++ b/man/ipsilon.7
@@ -1,6 +1,6 @@
 .\" Copyright (C) 2015 Ipsilon Project Contributors
 .\"
-.TH "ipsilon" "7" "2.0.1" "Ipsilon" "Ipsilon Manual Pages"
+.TH "ipsilon" "7" "2.0.2" "Ipsilon" "Ipsilon Manual Pages"
 .SH "NAME"
 ipsilon
 .SH "DESCRIPTION"
diff --git a/man/ipsilon.conf.5 b/man/ipsilon.conf.5
index 5c972de..2f5a8a1 100644
--- a/man/ipsilon.conf.5
+++ b/man/ipsilon.conf.5
@@ -1,6 +1,6 @@
 .\" Copyright (C) 2015 Ipsilon Project Contributors
 .\"
-.TH "ipsilon.conf" "5" "2.0.1" "Ipsilon" "Ipsilon Manual Pages"
+.TH "ipsilon.conf" "5" "2.0.2" "Ipsilon" "Ipsilon Manual Pages"
 .SH "NAME"
 ipsilon.conf \- Ipsilon IdP configuration file
 .SH "SYNOPSIS"
diff --git a/setup.py b/setup.py
index abe6db1..6ee74e4 100755
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@ DATA = 'share/ipsilon/'
 
 setup(
     name = 'ipsilon',
-    version = '2.0.1',
+    version = '2.0.2',
     license = 'GPLv3+',
     maintainer = 'Ipsilon project Contributors',
     maintainer_email = 'ipsilon@lists.fedorahosted.org',

