VYPR
Moderate severityNVD Advisory· Published Jul 31, 2012· Updated Apr 29, 2026

CVE-2012-3426

CVE-2012-3426

Description

OpenStack Keystone before 2012.1.1, as used in OpenStack Folsom before Folsom-1 and OpenStack Essex, does not properly implement token expiration, which allows remote authenticated users to bypass intended authorization restrictions by (1) creating new tokens through token chaining, (2) leveraging possession of a token for a disabled user account, or (3) leveraging possession of a token for an account with a changed password.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
KeystonePyPI
< 8.0.0a08.0.0a0

Affected products

4
  • cpe:2.3:a:openstack:essex:*:*:*:*:*:*:*:*
  • cpe:2.3:a:openstack:horizon:folsom-1:*:*:*:*:*:*:*
  • cpe:2.3:a:openstack:keystone:2012.1:*:*:*:*:*:*:*+ 1 more
    • cpe:2.3:a:openstack:keystone:2012.1:*:*:*:*:*:*:*
    • cpe:2.3:a:openstack:keystone:2012.1.1:*:*:*:*:*:*:*

Patches

6
375838cfceb8

Carrying over token expiry time when token chaining

https://github.com/openstack/keystoneDerek HigginsJun 5, 2012via ghsa
2 files changed · +14 1
  • keystone/service.py+2 1 modified
    @@ -351,7 +351,8 @@ def authenticate(self, context, auth=None):
                         context, token_id, dict(id=token_id,
                                                 user=user_ref,
                                                 tenant=tenant_ref,
    -                                            metadata=metadata_ref))
    +                                            metadata=metadata_ref,
    +                                            expires=old_token_ref['expires']))
     
             # TODO(termie): optimize this call at some point and put it into the
             #               the return for metadata
    
  • tests/test_keystoneclient.py+12 0 modified
    @@ -14,6 +14,7 @@
     # License for the specific language governing permissions and limitations
     # under the License.
     
    +import time
     import uuid
     
     import nose.exc
    @@ -333,6 +334,17 @@ def test_disable_user_invalidates_token(self):
                               self.get_client,
                               self.user_foo)
     
    +    def test_token_expiry_maintained(self):
    +        foo_client = self.get_client(self.user_foo)
    +        orig_token = foo_client.service_catalog.catalog['token']
    +
    +        time.sleep(1.01)
    +        reauthenticated_token = foo_client.tokens.authenticate(
    +                                    token=foo_client.auth_token)
    +
    +        self.assertEquals(orig_token['expires'],
    +                          reauthenticated_token.expires)
    +
         def test_user_create_update_delete(self):
             from keystoneclient import exceptions as client_exceptions
     
    
29e74e73a6e5

Carrying over token expiry time when token chaining

https://github.com/openstack/keystoneDerek HigginsJun 5, 2012via ghsa
2 files changed · +14 1
  • keystone/service.py+2 1 modified
    @@ -351,7 +351,8 @@ def authenticate(self, context, auth=None):
                         context, token_id, dict(id=token_id,
                                                 user=user_ref,
                                                 tenant=tenant_ref,
    -                                            metadata=metadata_ref))
    +                                            metadata=metadata_ref,
    +                                            expires=old_token_ref['expires']))
     
             # TODO(termie): optimize this call at some point and put it into the
             #               the return for metadata
    
  • tests/test_keystoneclient.py+12 0 modified
    @@ -14,6 +14,7 @@
     # License for the specific language governing permissions and limitations
     # under the License.
     
    +import time
     import uuid
     
     import nose.exc
    @@ -326,6 +327,17 @@ def test_disable_user_invalidates_token(self):
                               self.get_client,
                               self.user_foo)
     
    +    def test_token_expiry_maintained(self):
    +        foo_client = self.get_client(self.user_foo)
    +        orig_token = foo_client.service_catalog.catalog['token']
    +
    +        time.sleep(1.01)
    +        reauthenticated_token = foo_client.tokens.authenticate(
    +                                    token=foo_client.auth_token)
    +
    +        self.assertEquals(orig_token['expires'],
    +                          reauthenticated_token.expires)
    +
         def test_user_create_update_delete(self):
             from keystoneclient import exceptions as client_exceptions
     
    
d9600434da14

Invalidate user tokens when a user is disabled

https://github.com/openstack/keystoneDerek HigginsMay 11, 2012via ghsa
3 files changed · +44 13
  • keystone/identity/core.py+12 10 modified
    @@ -408,6 +408,17 @@ def update_user(self, context, user_id, user):
                 raise exception.UserNotFound(user_id=user_id)
     
             user_ref = self.identity_api.update_user(context, user_id, user)
    +
    +        # If the password was changed or the user was disabled we clear tokens
    +        if user.get('password') or user.get('enabled', True) == False:
    +            try:
    +                for token_id in self.token_api.list_tokens(context, user_id):
    +                    self.token_api.delete_token(context, token_id)
    +            except exception.NotImplemented:
    +                # The users status has been changed but tokens remain valid for
    +                # backends that can't list tokens for users
    +                LOG.warning('User %s status has changed, but existing tokens '
    +                            'remain valid' % user_id)
             return {'user': user_ref}
     
         def delete_user(self, context, user_id):
    @@ -421,16 +432,7 @@ def set_user_enabled(self, context, user_id, user):
             return self.update_user(context, user_id, user)
     
         def set_user_password(self, context, user_id, user):
    -        user_ref = self.update_user(context, user_id, user)
    -        try:
    -            for token_id in self.token_api.list_tokens(context, user_id):
    -                self.token_api.delete_token(context, token_id)
    -        except exception.NotImplemented:
    -            # The password has been changed but tokens remain valid for
    -            # backends that can't list tokens for users
    -            LOG.warning('Password changed for %s, but existing tokens remain '
    -                        'valid' % user_id)
    -        return user_ref
    +        return self.update_user(context, user_id, user)
     
         def update_user_tenant(self, context, user_id, user):
             """Update the default tenant."""
    
  • keystone/service.py+13 1 modified
    @@ -28,6 +28,9 @@
     from keystone.common import wsgi
     
     
    +LOG = logging.getLogger(__name__)
    +
    +
     class AdminRouter(wsgi.ComposingRouter):
         def __init__(self):
             mapper = routes.Mapper()
    @@ -275,7 +278,8 @@ def authenticate(self, context, auth=None):
     
                     # If the user is disabled don't allow them to authenticate
                     if not user_ref.get('enabled', True):
    -                    raise exception.Forbidden(message='User has been disabled')
    +                    LOG.warning('User %s is disabled' % user_id)
    +                    raise exception.Unauthorized()
                 except AssertionError as e:
                     raise exception.Unauthorized(e.message)
     
    @@ -314,6 +318,14 @@ def authenticate(self, context, auth=None):
     
                 user_ref = old_token_ref['user']
     
    +            # If the user is disabled don't allow them to authenticate
    +            current_user_ref = self.identity_api.get_user(
    +                                                    context=context,
    +                                                    user_id=user_ref['id'])
    +            if not current_user_ref.get('enabled', True):
    +                LOG.warning('User %s is disabled' % user_ref['id'])
    +                raise exception.Unauthorized()
    +
                 tenants = self.identity_api.get_tenants_for_user(context,
                                                                  user_ref['id'])
                 if tenant_id:
    
  • tests/test_keystoneclient.py+19 2 modified
    @@ -309,6 +309,23 @@ def test_change_password_invalidates_token(self):
                               client.tokens.authenticate,
                               token=token_id)
     
    +    def test_disable_user_invalidates_token(self):
    +        from keystoneclient import exceptions as client_exceptions
    +
    +        admin_client = self.get_client(admin=True)
    +        foo_client = self.get_client(self.user_foo)
    +
    +        admin_client.users.update_enabled(user=self.user_foo['id'],
    +                                          enabled=False)
    +
    +        self.assertRaises(client_exceptions.Unauthorized,
    +                          foo_client.tokens.authenticate,
    +                          token=foo_client.auth_token)
    +
    +        self.assertRaises(client_exceptions.Unauthorized,
    +                          self.get_client,
    +                          self.user_foo)
    +
         def test_user_create_update_delete(self):
             from keystoneclient import exceptions as client_exceptions
     
    @@ -332,7 +349,7 @@ def test_user_create_update_delete(self):
             user = client.users.get(user.id)
             self.assertFalse(user.enabled)
     
    -        self.assertRaises(client_exceptions.AuthorizationFailure,
    +        self.assertRaises(client_exceptions.Unauthorized,
                       self._client,
                       username=test_username,
                       password='password')
    @@ -871,7 +888,7 @@ def test_user_create_update_delete(self):
             user = client.users.get(user.id)
             self.assertFalse(user.enabled)
     
    -        self.assertRaises(client_exceptions.AuthorizationFailure,
    +        self.assertRaises(client_exceptions.Unauthorized,
                       self._client,
                       username=test_username,
                       password='password')
    
628149b3dc6b

Invalidate user tokens when a user is disabled

https://github.com/openstack/keystoneDerek HigginsMay 11, 2012via ghsa
3 files changed · +44 13
  • keystone/identity/core.py+12 10 modified
    @@ -408,6 +408,17 @@ def update_user(self, context, user_id, user):
                 raise exception.UserNotFound(user_id=user_id)
     
             user_ref = self.identity_api.update_user(context, user_id, user)
    +
    +        # If the password was changed or the user was disabled we clear tokens
    +        if user.get('password') or user.get('enabled', True) == False:
    +            try:
    +                for token_id in self.token_api.list_tokens(context, user_id):
    +                    self.token_api.delete_token(context, token_id)
    +            except exception.NotImplemented:
    +                # The users status has been changed but tokens remain valid for
    +                # backends that can't list tokens for users
    +                LOG.warning('User %s status has changed, but existing tokens '
    +                            'remain valid' % user_id)
             return {'user': user_ref}
     
         def delete_user(self, context, user_id):
    @@ -421,16 +432,7 @@ def set_user_enabled(self, context, user_id, user):
             return self.update_user(context, user_id, user)
     
         def set_user_password(self, context, user_id, user):
    -        user_ref = self.update_user(context, user_id, user)
    -        try:
    -            for token_id in self.token_api.list_tokens(context, user_id):
    -                self.token_api.delete_token(context, token_id)
    -        except exception.NotImplemented:
    -            # The password has been changed but tokens remain valid for
    -            # backends that can't list tokens for users
    -            LOG.warning('Password changed for %s, but existing tokens remain '
    -                        'valid' % user_id)
    -        return user_ref
    +        return self.update_user(context, user_id, user)
     
         def update_user_tenant(self, context, user_id, user):
             """Update the default tenant."""
    
  • keystone/service.py+13 1 modified
    @@ -28,6 +28,9 @@
     from keystone.common import wsgi
     
     
    +LOG = logging.getLogger(__name__)
    +
    +
     class AdminRouter(wsgi.ComposingRouter):
         def __init__(self):
             mapper = routes.Mapper()
    @@ -275,7 +278,8 @@ def authenticate(self, context, auth=None):
     
                     # If the user is disabled don't allow them to authenticate
                     if not user_ref.get('enabled', True):
    -                    raise exception.Forbidden(message='User has been disabled')
    +                    LOG.warning('User %s is disabled' % user_id)
    +                    raise exception.Unauthorized()
                 except AssertionError as e:
                     raise exception.Unauthorized(e.message)
     
    @@ -314,6 +318,14 @@ def authenticate(self, context, auth=None):
     
                 user_ref = old_token_ref['user']
     
    +            # If the user is disabled don't allow them to authenticate
    +            current_user_ref = self.identity_api.get_user(
    +                                                    context=context,
    +                                                    user_id=user_ref['id'])
    +            if not current_user_ref.get('enabled', True):
    +                LOG.warning('User %s is disabled' % user_ref['id'])
    +                raise exception.Unauthorized()
    +
                 tenants = self.identity_api.get_tenants_for_user(context,
                                                                  user_ref['id'])
                 if tenant_id:
    
  • tests/test_keystoneclient.py+19 2 modified
    @@ -309,6 +309,23 @@ def test_change_password_invalidates_token(self):
                               client.tokens.authenticate,
                               token=token_id)
     
    +    def test_disable_user_invalidates_token(self):
    +        from keystoneclient import exceptions as client_exceptions
    +
    +        admin_client = self.get_client(admin=True)
    +        foo_client = self.get_client(self.user_foo)
    +
    +        admin_client.users.update_enabled(user=self.user_foo['id'],
    +                                          enabled=False)
    +
    +        self.assertRaises(client_exceptions.Unauthorized,
    +                          foo_client.tokens.authenticate,
    +                          token=foo_client.auth_token)
    +
    +        self.assertRaises(client_exceptions.Unauthorized,
    +                          self.get_client,
    +                          self.user_foo)
    +
         def test_user_create_update_delete(self):
             from keystoneclient import exceptions as client_exceptions
     
    @@ -332,7 +349,7 @@ def test_user_create_update_delete(self):
             user = client.users.get(user.id)
             self.assertFalse(user.enabled)
     
    -        self.assertRaises(client_exceptions.AuthorizationFailure,
    +        self.assertRaises(client_exceptions.Unauthorized,
                       self._client,
                       username=test_username,
                       password='password')
    @@ -880,7 +897,7 @@ def test_user_create_update_delete(self):
             user = client.users.get(user.id)
             self.assertFalse(user.enabled)
     
    -        self.assertRaises(client_exceptions.AuthorizationFailure,
    +        self.assertRaises(client_exceptions.Unauthorized,
                       self._client,
                       username=test_username,
                       password='password')
    
ea03d05ed5de

Invalidate user tokens when password is changed

https://github.com/openstack/keystoneDerek HigginsMay 9, 2012via ghsa
6 files changed · +76 1
  • AUTHORS+1 0 modified
    @@ -25,6 +25,7 @@ Darren Birkett <darren.birkett@gmail.com>
     dcramer <david.cramer@rackspace.com>
     Dean Troyer <dtroyer@gmail.com>
     Deepak Garg <deepakgarg.iitg@gmail.com>
    +Derek Higgins <derekh@redhat.com>
     Devin Carlen <devin.carlen@gmail.com>
     Dolph Mathews <dolph.mathews@gmail.com>
     Dolph Mathews <dolph.mathews@rackspace.com>
    
  • keystone/identity/core.py+13 1 modified
    @@ -24,12 +24,15 @@
     from keystone import exception
     from keystone import policy
     from keystone import token
    +from keystone.common import logging
     from keystone.common import manager
     from keystone.common import wsgi
     
     
     CONF = config.CONF
     
    +LOG = logging.getLogger(__name__)
    +
     
     class Manager(manager.Manager):
         """Default pivot point for the Identity backend.
    @@ -418,7 +421,16 @@ def set_user_enabled(self, context, user_id, user):
             return self.update_user(context, user_id, user)
     
         def set_user_password(self, context, user_id, user):
    -        return self.update_user(context, user_id, user)
    +        user_ref = self.update_user(context, user_id, user)
    +        try:
    +            for token_id in self.token_api.list_tokens(context, user_id):
    +                self.token_api.delete_token(context, token_id)
    +        except exception.NotImplemented:
    +            # The password has been changed but tokens remain valid for
    +            # backends that can't list tokens for users
    +            LOG.warning('Password changed for %s, but existing tokens remain '
    +                        'valid' % user_id)
    +        return user_ref
     
         def update_user_tenant(self, context, user_id, user):
             """Update the default tenant."""
    
  • keystone/token/backends/kvs.py+15 0 modified
    @@ -44,3 +44,18 @@ def delete_token(self, token_id):
                 return self.db.delete('token-%s' % token_id)
             except KeyError:
                 raise exception.TokenNotFound(token_id=token_id)
    +
    +    def list_tokens(self, user_id):
    +        tokens = []
    +        now = datetime.datetime.utcnow()
    +        for token, user_ref in self.db.items():
    +            if not token.startswith('token-'):
    +                continue
    +            if 'user' not in user_ref:
    +                continue
    +            if user_ref['user'].get('id') != user_id:
    +                continue
    +            if user_ref.get('expires') and user_ref.get('expires') < now:
    +                continue
    +            tokens.append(token.split('-', 1)[1])
    +        return tokens
    
  • keystone/token/backends/sql.py+14 0 modified
    @@ -81,3 +81,17 @@ def delete_token(self, token_id):
             with session.begin():
                 session.delete(token_ref)
                 session.flush()
    +
    +    def list_tokens(self, user_id):
    +        session = self.get_session()
    +        tokens = []
    +        now = datetime.datetime.utcnow()
    +        for token_ref in session.query(TokenModel)\
    +                                      .filter(TokenModel.expires > now):
    +            token_ref_dict = token_ref.to_dict()
    +            if 'user' not in token_ref_dict:
    +                continue
    +            if token_ref_dict['user'].get('id') != user_id:
    +                continue
    +            tokens.append(token_ref['id'])
    +        return tokens
    
  • keystone/token/core.py+10 0 modified
    @@ -87,6 +87,16 @@ def delete_token(self, token_id):
             """
             raise exception.NotImplemented()
     
    +    def list_tokens(self, user_id):
    +        """Returns a list of current token_id's for a user
    +
    +        :param user_id: identity of the user
    +        :type user_id: string
    +        :returns: list of token_id's
    +
    +        """
    +        raise exception.NotImplemented()
    +
         def _get_default_expire_time(self):
             """Determine when a token should expire based on the config.
     
    
  • tests/test_keystoneclient.py+23 0 modified
    @@ -286,6 +286,29 @@ def test_invalid_user_password(self):
                               username='blah',
                               password='blah')
     
    +    def test_change_password_invalidates_token(self):
    +        from keystoneclient import exceptions as client_exceptions
    +
    +        client = self.get_client(admin=True)
    +
    +        username = uuid.uuid4().hex
    +        passwd = uuid.uuid4().hex
    +        user = client.users.create(name=username, password=passwd,
    +                                   email=uuid.uuid4().hex)
    +
    +        token_id = client.tokens.authenticate(username=username,
    +                                              password=passwd).id
    +
    +        # authenticate with a token should work before a password change
    +        client.tokens.authenticate(token=token_id)
    +
    +        client.users.update_password(user=user.id, password=uuid.uuid4().hex)
    +
    +        # authenticate with a token should not work after a password change
    +        self.assertRaises(client_exceptions.Unauthorized,
    +                          client.tokens.authenticate,
    +                          token=token_id)
    +
         def test_user_create_update_delete(self):
             from keystoneclient import exceptions as client_exceptions
     
    
a67b24878a61

Invalidate user tokens when password is changed

https://github.com/openstack/keystoneDerek HigginsMay 9, 2012via ghsa
5 files changed · +75 1
  • keystone/identity/core.py+13 1 modified
    @@ -24,12 +24,15 @@
     from keystone import exception
     from keystone import policy
     from keystone import token
    +from keystone.common import logging
     from keystone.common import manager
     from keystone.common import wsgi
     
     
     CONF = config.CONF
     
    +LOG = logging.getLogger(__name__)
    +
     
     class Manager(manager.Manager):
         """Default pivot point for the Identity backend.
    @@ -418,7 +421,16 @@ def set_user_enabled(self, context, user_id, user):
             return self.update_user(context, user_id, user)
     
         def set_user_password(self, context, user_id, user):
    -        return self.update_user(context, user_id, user)
    +        user_ref = self.update_user(context, user_id, user)
    +        try:
    +            for token_id in self.token_api.list_tokens(context, user_id):
    +                self.token_api.delete_token(context, token_id)
    +        except exception.NotImplemented:
    +            # The password has been changed but tokens remain valid for
    +            # backends that can't list tokens for users
    +            LOG.warning('Password changed for %s, but existing tokens remain '
    +                        'valid' % user_id)
    +        return user_ref
     
         def update_user_tenant(self, context, user_id, user):
             """Update the default tenant."""
    
  • keystone/token/backends/kvs.py+15 0 modified
    @@ -44,3 +44,18 @@ def delete_token(self, token_id):
                 return self.db.delete('token-%s' % token_id)
             except KeyError:
                 raise exception.TokenNotFound(token_id=token_id)
    +
    +    def list_tokens(self, user_id):
    +        tokens = []
    +        now = datetime.datetime.utcnow()
    +        for token, user_ref in self.db.items():
    +            if not token.startswith('token-'):
    +                continue
    +            if 'user' not in user_ref:
    +                continue
    +            if user_ref['user'].get('id') != user_id:
    +                continue
    +            if user_ref.get('expires') and user_ref.get('expires') < now:
    +                continue
    +            tokens.append(token.split('-', 1)[1])
    +        return tokens
    
  • keystone/token/backends/sql.py+14 0 modified
    @@ -81,3 +81,17 @@ def delete_token(self, token_id):
             with session.begin():
                 session.delete(token_ref)
                 session.flush()
    +
    +    def list_tokens(self, user_id):
    +        session = self.get_session()
    +        tokens = []
    +        now = datetime.datetime.utcnow()
    +        for token_ref in session.query(TokenModel)\
    +                                      .filter(TokenModel.expires > now):
    +            token_ref_dict = token_ref.to_dict()
    +            if 'user' not in token_ref_dict:
    +                continue
    +            if token_ref_dict['user'].get('id') != user_id:
    +                continue
    +            tokens.append(token_ref['id'])
    +        return tokens
    
  • keystone/token/core.py+10 0 modified
    @@ -87,6 +87,16 @@ def delete_token(self, token_id):
             """
             raise exception.NotImplemented()
     
    +    def list_tokens(self, user_id):
    +        """Returns a list of current token_id's for a user
    +
    +        :param user_id: identity of the user
    +        :type user_id: string
    +        :returns: list of token_id's
    +
    +        """
    +        raise exception.NotImplemented()
    +
         def _get_default_expire_time(self):
             """Determine when a token should expire based on the config.
     
    
  • tests/test_keystoneclient.py+23 0 modified
    @@ -286,6 +286,29 @@ def test_invalid_user_password(self):
                               username='blah',
                               password='blah')
     
    +    def test_change_password_invalidates_token(self):
    +        from keystoneclient import exceptions as client_exceptions
    +
    +        client = self.get_client(admin=True)
    +
    +        username = uuid.uuid4().hex
    +        passwd = uuid.uuid4().hex
    +        user = client.users.create(name=username, password=passwd,
    +                                   email=uuid.uuid4().hex)
    +
    +        token_id = client.tokens.authenticate(username=username,
    +                                              password=passwd).id
    +
    +        # authenticate with a token should work before a password change
    +        client.tokens.authenticate(token=token_id)
    +
    +        client.users.update_password(user=user.id, password=uuid.uuid4().hex)
    +
    +        # authenticate with a token should not work after a password change
    +        self.assertRaises(client_exceptions.Unauthorized,
    +                          client.tokens.authenticate,
    +                          token=token_id)
    +
         def test_user_create_update_delete(self):
             from keystoneclient import exceptions as client_exceptions
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

20

News mentions

0

No linked articles in our index yet.