VYPR
High severityNVD Advisory· Published Aug 25, 2014· Updated May 6, 2026

CVE-2014-5251

CVE-2014-5251

Description

The MySQL token driver in OpenStack Identity (Keystone) 2014.1.x before 2014.1.2.1 and Juno before Juno-3 stores timestamps with the incorrect precision, which causes the expiration comparison for tokens to fail and allows remote authenticated users to retain access via an expired token.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
keystonePyPI
< 8.0.0a08.0.0a0

Affected products

5
  • cpe:2.3:a:openstack:keystone:2014.1:*:*:*:*:*:*:*+ 3 more
    • cpe:2.3:a:openstack:keystone:2014.1:*:*:*:*:*:*:*
    • cpe:2.3:a:openstack:keystone:2014.1.2:*:*:*:*:*:*:*
    • cpe:2.3:a:openstack:keystone:juno-1:*:*:*:*:*:*:*
    • cpe:2.3:a:openstack:keystone:juno-2:*:*:*:*:*:*:*
  • cpe:2.3:o:canonical:ubuntu_linux:14.04:*:*:*:lts:*:*:*

Patches

2
6cbf835542d6

Fix revocation event handling with MySQL

https://github.com/openstack/keystoneBrant KnudsonJul 31, 2014via ghsa
4 files changed · +31 18
  • keystone/contrib/revoke/model.py+22 6 modified
    @@ -69,6 +69,11 @@ def __init__(self, **kwargs):
                 # This is revoking all tokens for a domain.
                 self.domain_scope_id = None
     
    +        if self.expires_at is not None:
    +            # Trim off the expiration time because MySQL timestamps are only
    +            # accurate to the second.
    +            self.expires_at = self.expires_at.replace(microsecond=0)
    +
             if self.revoked_at is None:
                 self.revoked_at = timeutils.utcnow()
             if self.issued_before is None:
    @@ -90,8 +95,7 @@ def to_dict(self):
             if self.consumer_id is not None:
                 event['OS-OAUTH1:access_token_id'] = self.access_token_id
             if self.expires_at is not None:
    -            event['expires_at'] = timeutils.isotime(self.expires_at,
    -                                                    subsecond=True)
    +            event['expires_at'] = timeutils.isotime(self.expires_at)
             if self.issued_before is not None:
                 event['issued_before'] = timeutils.isotime(self.issued_before,
                                                            subsecond=True)
    @@ -242,9 +246,15 @@ def is_revoked(self, token_data):
     
     def build_token_values_v2(access, default_domain_id):
         token_data = access['token']
    +
    +    token_expires_at = timeutils.parse_isotime(token_data['expires'])
    +
    +    # Trim off the microseconds because the revocation event only has
    +    # expirations accurate to the second.
    +    token_expires_at = token_expires_at.replace(microsecond=0)
    +
         token_values = {
    -        'expires_at': timeutils.normalize_time(
    -            timeutils.parse_isotime(token_data['expires'])),
    +        'expires_at': timeutils.normalize_time(token_expires_at),
             'issued_at': timeutils.normalize_time(
                 timeutils.parse_isotime(token_data['issued_at']))}
     
    @@ -282,9 +292,15 @@ def build_token_values_v2(access, default_domain_id):
     
     
     def build_token_values(token_data):
    +
    +    token_expires_at = timeutils.parse_isotime(token_data['expires_at'])
    +
    +    # Trim off the microseconds because the revocation event only has
    +    # expirations accurate to the second.
    +    token_expires_at = token_expires_at.replace(microsecond=0)
    +
         token_values = {
    -        'expires_at': timeutils.normalize_time(
    -            timeutils.parse_isotime(token_data['expires_at'])),
    +        'expires_at': timeutils.normalize_time(token_expires_at),
             'issued_at': timeutils.normalize_time(
                 timeutils.parse_isotime(token_data['issued_at']))}
     
    
  • keystone/tests/test_revoke.py+3 3 modified
    @@ -350,7 +350,7 @@ def test_by_user_expiration(self):
             event = self._revoke_by_expiration(user_id, future_time)
             token_data_1 = _sample_blank_token()
             token_data_1['user_id'] = user_id
    -        token_data_1['expires_at'] = future_time
    +        token_data_1['expires_at'] = future_time.replace(microsecond=0)
             self._assertTokenRevoked(token_data_1)
     
             token_data_2 = _sample_blank_token()
    @@ -375,7 +375,7 @@ def test_by_user_project(self):
             token_data = _sample_blank_token()
             token_data['user_id'] = user_id
             token_data['project_id'] = project_id
    -        token_data['expires_at'] = future_time
    +        token_data['expires_at'] = future_time.replace(microsecond=0)
     
             self._revoke_by_expiration(user_id, future_time, project_id=project_id)
             self._assertTokenRevoked(token_data)
    @@ -392,7 +392,7 @@ def test_by_user_domain(self):
             token_data = _sample_blank_token()
             token_data['user_id'] = user_id
             token_data['assignment_domain_id'] = domain_id
    -        token_data['expires_at'] = future_time
    +        token_data['expires_at'] = future_time.replace(microsecond=0)
     
             self._revoke_by_expiration(user_id, future_time, domain_id=domain_id)
             self._assertTokenRevoked(token_data)
    
  • keystone/tests/test_v3_auth.py+5 7 modified
    @@ -1441,6 +1441,11 @@ def test_disable_domain_shows_in_event_list(self):
         def assertUserAndExpiryInList(self, events, user_id, expires_at):
             found = False
             for e in events:
    +
    +            # Timestamps in the event list are accurate to second.
    +            expires_at = timeutils.parse_isotime(expires_at)
    +            expires_at = timeutils.isotime(expires_at)
    +
                 if e['user_id'] == user_id and e['expires_at'] == expires_at:
                     found = True
             self.assertTrue(found,
    @@ -1464,14 +1469,9 @@ def test_list_delete_token_shows_in_event_list(self):
             response.json_body['token']
             headers3 = {'X-Subject-Token': response.headers['X-Subject-Token']}
     
    -        scoped_token = self.get_scoped_token()
    -        headers_unrevoked = {'X-Subject-Token': scoped_token}
    -
             self.head('/auth/tokens', headers=headers, expected_status=200)
             self.head('/auth/tokens', headers=headers2, expected_status=200)
             self.head('/auth/tokens', headers=headers3, expected_status=200)
    -        self.head('/auth/tokens', headers=headers_unrevoked,
    -                  expected_status=200)
     
             self.delete('/auth/tokens', headers=headers, expected_status=204)
             # NOTE(ayoung): not deleting token3, as it should be deleted
    @@ -1488,8 +1488,6 @@ def test_list_delete_token_shows_in_event_list(self):
             self.head('/auth/tokens', headers=headers, expected_status=404)
             self.head('/auth/tokens', headers=headers2, expected_status=200)
             self.head('/auth/tokens', headers=headers3, expected_status=200)
    -        self.head('/auth/tokens', headers=headers_unrevoked,
    -                  expected_status=200)
     
         def test_list_with_filter(self):
     
    
  • keystone/tests/test_v3_os_revoke.py+1 2 modified
    @@ -62,8 +62,7 @@ def test_revoked_token_in_list(self):
             expires_at = token.default_expire_time()
             sample = self._blank_event()
             sample['user_id'] = unicode(user_id)
    -        sample['expires_at'] = unicode(timeutils.isotime(expires_at,
    -                                                         subsecond=True))
    +        sample['expires_at'] = unicode(timeutils.isotime(expires_at))
             before_time = timeutils.utcnow()
             self.revoke_api.revoke_by_expiration(user_id, expires_at)
             resp = self.get('/OS-REVOKE/events')
    
7aee6304f653

Fix revocation event handling with MySQL

https://github.com/openstack/keystoneBrant KnudsonJul 31, 2014via ghsa
4 files changed · +31 18
  • keystone/contrib/revoke/model.py+22 6 modified
    @@ -69,6 +69,11 @@ def __init__(self, **kwargs):
                 # This is revoking all tokens for a domain.
                 self.domain_scope_id = None
     
    +        if self.expires_at is not None:
    +            # Trim off the expiration time because MySQL timestamps are only
    +            # accurate to the second.
    +            self.expires_at = self.expires_at.replace(microsecond=0)
    +
             if self.revoked_at is None:
                 self.revoked_at = timeutils.utcnow()
             if self.issued_before is None:
    @@ -90,8 +95,7 @@ def to_dict(self):
             if self.consumer_id is not None:
                 event['OS-OAUTH1:access_token_id'] = self.access_token_id
             if self.expires_at is not None:
    -            event['expires_at'] = timeutils.isotime(self.expires_at,
    -                                                    subsecond=True)
    +            event['expires_at'] = timeutils.isotime(self.expires_at)
             if self.issued_before is not None:
                 event['issued_before'] = timeutils.isotime(self.issued_before,
                                                            subsecond=True)
    @@ -242,9 +246,15 @@ def is_revoked(self, token_data):
     
     def build_token_values_v2(access, default_domain_id):
         token_data = access['token']
    +
    +    token_expires_at = timeutils.parse_isotime(token_data['expires'])
    +
    +    # Trim off the microseconds because the revocation event only has
    +    # expirations accurate to the second.
    +    token_expires_at = token_expires_at.replace(microsecond=0)
    +
         token_values = {
    -        'expires_at': timeutils.normalize_time(
    -            timeutils.parse_isotime(token_data['expires'])),
    +        'expires_at': timeutils.normalize_time(token_expires_at),
             'issued_at': timeutils.normalize_time(
                 timeutils.parse_isotime(token_data['issued_at']))}
     
    @@ -282,9 +292,15 @@ def build_token_values_v2(access, default_domain_id):
     
     
     def build_token_values(token_data):
    +
    +    token_expires_at = timeutils.parse_isotime(token_data['expires_at'])
    +
    +    # Trim off the microseconds because the revocation event only has
    +    # expirations accurate to the second.
    +    token_expires_at = token_expires_at.replace(microsecond=0)
    +
         token_values = {
    -        'expires_at': timeutils.normalize_time(
    -            timeutils.parse_isotime(token_data['expires_at'])),
    +        'expires_at': timeutils.normalize_time(token_expires_at),
             'issued_at': timeutils.normalize_time(
                 timeutils.parse_isotime(token_data['issued_at']))}
     
    
  • keystone/tests/test_revoke.py+3 3 modified
    @@ -346,7 +346,7 @@ def test_by_user_expiration(self):
             event = self._revoke_by_expiration(user_id, future_time)
             token_data_1 = _sample_blank_token()
             token_data_1['user_id'] = user_id
    -        token_data_1['expires_at'] = future_time
    +        token_data_1['expires_at'] = future_time.replace(microsecond=0)
             self._assertTokenRevoked(token_data_1)
     
             token_data_2 = _sample_blank_token()
    @@ -371,7 +371,7 @@ def test_by_user_project(self):
             token_data = _sample_blank_token()
             token_data['user_id'] = user_id
             token_data['project_id'] = project_id
    -        token_data['expires_at'] = future_time
    +        token_data['expires_at'] = future_time.replace(microsecond=0)
     
             self._revoke_by_expiration(user_id, future_time, project_id=project_id)
             self._assertTokenRevoked(token_data)
    @@ -388,7 +388,7 @@ def test_by_user_domain(self):
             token_data = _sample_blank_token()
             token_data['user_id'] = user_id
             token_data['assignment_domain_id'] = domain_id
    -        token_data['expires_at'] = future_time
    +        token_data['expires_at'] = future_time.replace(microsecond=0)
     
             self._revoke_by_expiration(user_id, future_time, domain_id=domain_id)
             self._assertTokenRevoked(token_data)
    
  • keystone/tests/test_v3_auth.py+5 7 modified
    @@ -1464,6 +1464,11 @@ def test_disable_domain_shows_in_event_list(self):
         def assertUserAndExpiryInList(self, events, user_id, expires_at):
             found = False
             for e in events:
    +
    +            # Timestamps in the event list are accurate to second.
    +            expires_at = timeutils.parse_isotime(expires_at)
    +            expires_at = timeutils.isotime(expires_at)
    +
                 if e['user_id'] == user_id and e['expires_at'] == expires_at:
                     found = True
             self.assertTrue(found,
    @@ -1487,14 +1492,9 @@ def test_list_delete_token_shows_in_event_list(self):
             response.json_body['token']
             headers3 = {'X-Subject-Token': response.headers['X-Subject-Token']}
     
    -        scoped_token = self.get_scoped_token()
    -        headers_unrevoked = {'X-Subject-Token': scoped_token}
    -
             self.head('/auth/tokens', headers=headers, expected_status=200)
             self.head('/auth/tokens', headers=headers2, expected_status=200)
             self.head('/auth/tokens', headers=headers3, expected_status=200)
    -        self.head('/auth/tokens', headers=headers_unrevoked,
    -                  expected_status=200)
     
             self.delete('/auth/tokens', headers=headers, expected_status=204)
             # NOTE(ayoung): not deleting token3, as it should be deleted
    @@ -1511,8 +1511,6 @@ def test_list_delete_token_shows_in_event_list(self):
             self.head('/auth/tokens', headers=headers, expected_status=404)
             self.head('/auth/tokens', headers=headers2, expected_status=200)
             self.head('/auth/tokens', headers=headers3, expected_status=200)
    -        self.head('/auth/tokens', headers=headers_unrevoked,
    -                  expected_status=200)
     
         def test_list_with_filter(self):
     
    
  • keystone/tests/test_v3_os_revoke.py+1 2 modified
    @@ -64,8 +64,7 @@ def test_revoked_token_in_list(self):
             expires_at = provider.default_expire_time()
             sample = self._blank_event()
             sample['user_id'] = six.text_type(user_id)
    -        sample['expires_at'] = six.text_type(timeutils.isotime(expires_at,
    -                                                               subsecond=True))
    +        sample['expires_at'] = six.text_type(timeutils.isotime(expires_at))
             before_time = timeutils.utcnow()
             self.revoke_api.revoke_by_expiration(user_id, expires_at)
             resp = self.get('/OS-REVOKE/events')
    

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

10

News mentions

0

No linked articles in our index yet.