High severityNVD Advisory· Published Aug 25, 2014· Updated May 6, 2026
CVE-2014-5252
CVE-2014-5252
Description
The V3 API in OpenStack Identity (Keystone) 2014.1.x before 2014.1.2.1 and Juno before Juno-3 updates the issued_at value for UUID v2 tokens, which allows remote authenticated users to bypass the token expiration and retain access via a verification (1) GET or (2) HEAD request to v3/auth/tokens/.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
keystonePyPI | < 8.0.0a0 | 8.0.0a0 |
Affected products
5- cpe:2.3:o:canonical:ubuntu_linux:14.04:*:*:*:lts:*:*:*
Patches
3bdb88c662ac2Fix for V2 token issued_at time changing
2 files changed · +14 −11
keystone/tests/test_v3_auth.py+2 −6 modified@@ -370,8 +370,7 @@ def test_v2_v3_token_intermix(self): v3_issued_at = timeutils.parse_isotime( token_data['token']['issued_at']) - # FIXME(blk-u): the following should be assertEqual, see bug 1348820 - self.assertNotEqual(v2_issued_at, v3_issued_at) + self.assertEqual(v2_issued_at, v3_issued_at) def test_rescoping_token(self): expires = self.token_data['token']['expires_at'] @@ -1225,9 +1224,6 @@ def get_v2_token(self): def test_revoke_v2_token_no_check(self): # Test that a V2 token can be revoked without validating it first. - # NOTE(blk-u): This doesn't work right. The token should be invalid - # after being revoked but it's not. See bug 1348820. - token = self.get_v2_token() self.delete('/auth/tokens', @@ -1236,7 +1232,7 @@ def test_revoke_v2_token_no_check(self): self.head('/auth/tokens', headers={'X-Subject-Token': token}, - expected_status=200) # FIXME(blk-u): This should be 404 + expected_status=404) @dependency.requires('revoke_api')
keystone/token/providers/common.py+12 −5 modified@@ -315,18 +315,20 @@ def _populate_service_catalog(self, token_data, user_id, # TODO(ayoung): Enforce Endpoints for trust token_data['catalog'] = service_catalog - def _populate_token_dates(self, token_data, expires=None, trust=None): + def _populate_token_dates(self, token_data, expires=None, trust=None, + issued_at=None): if not expires: expires = token.default_expire_time() if not isinstance(expires, six.string_types): expires = timeutils.isotime(expires, subsecond=True) token_data['expires_at'] = expires - token_data['issued_at'] = timeutils.isotime(subsecond=True) + token_data['issued_at'] = (issued_at or + timeutils.isotime(subsecond=True)) def get_token_data(self, user_id, method_names, extras, domain_id=None, project_id=None, expires=None, trust=None, token=None, include_catalog=True, - bind=None, access_token=None): + bind=None, access_token=None, issued_at=None): token_data = {'methods': method_names, 'extras': extras} @@ -350,7 +352,8 @@ def get_token_data(self, user_id, method_names, extras, if include_catalog: self._populate_service_catalog(token_data, user_id, domain_id, project_id, trust) - self._populate_token_dates(token_data, expires=expires, trust=trust) + self._populate_token_dates(token_data, expires=expires, trust=trust, + issued_at=issued_at) self._populate_oauth_section(token_data, access_token) return {'token': token_data} @@ -648,13 +651,17 @@ def _validate_v3_token_ref(self, token_ref): project_ref = token_ref.get('tenant') if project_ref: project_id = project_ref['id'] + + issued_at = token_ref['token_data']['access']['token']['issued_at'] + token_data = self.v3_token_data_helper.get_token_data( token_ref['user']['id'], ['password', 'token'], {}, project_id=project_id, bind=token_ref.get('bind'), - expires=token_ref['expires']) + expires=token_ref['expires'], + issued_at=issued_at) return token_data def validate_token(self, token_id):
a4c73e4382cbFix for V2 token issued_at time changing
2 files changed · +14 −11
keystone/tests/test_v3_auth.py+2 −6 modified@@ -370,8 +370,7 @@ def test_v2_v3_token_intermix(self): v3_issued_at = timeutils.parse_isotime( token_data['token']['issued_at']) - # FIXME(blk-u): the following should be assertEqual, see bug 1348820 - self.assertNotEqual(v2_issued_at, v3_issued_at) + self.assertEqual(v2_issued_at, v3_issued_at) def test_rescoping_token(self): expires = self.token_data['token']['expires_at'] @@ -1248,9 +1247,6 @@ def get_v2_token(self): def test_revoke_v2_token_no_check(self): # Test that a V2 token can be revoked without validating it first. - # NOTE(blk-u): This doesn't work right. The token should be invalid - # after being revoked but it's not. See bug 1348820. - token = self.get_v2_token() self.delete('/auth/tokens', @@ -1259,7 +1255,7 @@ def test_revoke_v2_token_no_check(self): self.head('/auth/tokens', headers={'X-Subject-Token': token}, - expected_status=200) # FIXME(blk-u): This should be 404 + expected_status=404) @dependency.requires('revoke_api')
keystone/token/providers/common.py+12 −5 modified@@ -310,18 +310,20 @@ def _populate_service_catalog(self, token_data, user_id, # TODO(ayoung): Enforce Endpoints for trust token_data['catalog'] = service_catalog - def _populate_token_dates(self, token_data, expires=None, trust=None): + def _populate_token_dates(self, token_data, expires=None, trust=None, + issued_at=None): if not expires: expires = provider.default_expire_time() if not isinstance(expires, six.string_types): expires = timeutils.isotime(expires, subsecond=True) token_data['expires_at'] = expires - token_data['issued_at'] = timeutils.isotime(subsecond=True) + token_data['issued_at'] = (issued_at or + timeutils.isotime(subsecond=True)) def get_token_data(self, user_id, method_names, extras, domain_id=None, project_id=None, expires=None, trust=None, token=None, include_catalog=True, - bind=None, access_token=None): + bind=None, access_token=None, issued_at=None): token_data = {'methods': method_names, 'extras': extras} @@ -345,7 +347,8 @@ def get_token_data(self, user_id, method_names, extras, if include_catalog: self._populate_service_catalog(token_data, user_id, domain_id, project_id, trust) - self._populate_token_dates(token_data, expires=expires, trust=trust) + self._populate_token_dates(token_data, expires=expires, trust=trust, + issued_at=issued_at) self._populate_oauth_section(token_data, access_token) return {'token': token_data} @@ -633,13 +636,17 @@ def _validate_v3_token_ref(self, token_ref): project_ref = token_ref.get('tenant') if project_ref: project_id = project_ref['id'] + + issued_at = token_ref['token_data']['access']['token']['issued_at'] + token_data = self.v3_token_data_helper.get_token_data( token_ref['user']['id'], ['password', 'token'], {}, project_id=project_id, bind=token_ref.get('bind'), - expires=token_ref['expires']) + expires=token_ref['expires'], + issued_at=issued_at) return token_data def validate_token(self, token_id):
556fb8603116Add tests related to V2 token issued_at time changing
1 file changed · +37 −12
keystone/tests/test_v3_auth.py+37 −12 modified@@ -365,6 +365,14 @@ def test_v2_v3_token_intermix(self): self.assertEqual(v2_token_data['access']['user']['roles'][0]['name'], token_data['token']['roles'][0]['name']) + v2_issued_at = timeutils.parse_isotime( + v2_token_data['access']['token']['issued_at']) + v3_issued_at = timeutils.parse_isotime( + token_data['token']['issued_at']) + + # FIXME(blk-u): the following should be assertEqual, see bug 1348820 + self.assertNotEqual(v2_issued_at, v3_issued_at) + def test_rescoping_token(self): expires = self.token_data['token']['expires_at'] auth_data = self.build_authentication_request( @@ -1224,6 +1232,35 @@ def test_deleting_project_deletes_grants(self): # Make sure that we get a NotFound(404) when heading that role. self.head(role_path, expected_status=404) + def get_v2_token(self): + body = { + 'auth': { + 'passwordCredentials': { + 'username': self.default_domain_user['name'], + 'password': self.default_domain_user['password'], + } + }, + } + + r = self.admin_request(method='POST', path='/v2.0/tokens', body=body) + return r.json_body['access']['token']['id'] + + def test_revoke_v2_token_no_check(self): + # Test that a V2 token can be revoked without validating it first. + + # NOTE(blk-u): This doesn't work right. The token should be invalid + # after being revoked but it's not. See bug 1348820. + + token = self.get_v2_token() + + self.delete('/auth/tokens', + headers={'X-Subject-Token': token}, + expected_status=204) + + self.head('/auth/tokens', + headers={'X-Subject-Token': token}, + expected_status=200) # FIXME(blk-u): This should be 404 + @dependency.requires('revoke_api') class TestTokenRevokeApi(TestTokenRevokeById): @@ -1286,18 +1323,6 @@ def test_revoke_token(self): expected_status=200).json_body self.assertValidRevokedTokenResponse(events_response, self.user['id']) - def get_v2_token(self): - body = { - 'auth': { - 'passwordCredentials': { - 'username': self.default_domain_user['name'], - 'password': self.default_domain_user['password'], - }, - }, - } - r = self.admin_request(method='POST', path='/v2.0/tokens', body=body) - return r.json_body['access']['token']['id'] - def test_revoke_v2_token(self): token = self.get_v2_token() headers = {'X-Subject-Token': token}
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
11- github.com/advisories/GHSA-v8fq-gq9j-3v7hghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2014-5252ghsaADVISORY
- rhn.redhat.com/errata/RHSA-2014-1121.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2014-1122.htmlnvdWEB
- www.openwall.com/lists/oss-security/2014/08/15/6nvdWEB
- www.ubuntu.com/usn/USN-2324-1nvdWEB
- bugs.launchpad.net/keystone/+bug/1348820nvdWEB
- github.com/openstack/keystone/commit/556fb860311675fc437585651e4602b2908451ebghsaWEB
- github.com/openstack/keystone/commit/a4c73e4382cb062aa9f30fe1960d5014d3c49cc2ghsaWEB
- github.com/openstack/keystone/commit/bdb88c662ac2035f9b0d8a229a5db5f60f5f16aeghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/keystone/PYSEC-2014-108.yamlghsaWEB
News mentions
0No linked articles in our index yet.