CVE-2012-3426 (Python, published 2012-07-31T10:45Z)
Description: value: 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.
Severity: MEDIUM (CVSS v3 base score: )
Weakness: CWE-264 \u2014 Permissions, Privileges, and Access Controls
Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Repository: https://github.com/openstack/keystone
Files changed:
keystone/identity/core.py
keystone/token/backends/kvs.py
keystone/token/backends/sql.py
keystone/token/core.py
tests/test_keystoneclient.py
Commit message: Invalidate user tokens when password is changed

Fixes bug 996595

This commit will cause all valid tokens to be deleted for a user
who's password is changed (implemented for the sql and kvs backends)

Change-Id: I6ad7da8957b7041983a3fc91d9ba9368667d06ac


Vulnerable code:
        return self.update_user(context, user_id, user)

Fixed code:
from keystone.common import logging
LOG = logging.getLogger(__name__)

        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 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

    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
    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 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)