Medium severity6.5NVD Advisory· Published Apr 12, 2013· Updated Apr 29, 2026
CVE-2013-0270
CVE-2013-0270
Description
A flaw was found in OpenStack Keystone. A remote attacker could exploit this vulnerability by sending a large HTTP request, specifically by providing a long tenant name when requesting a token. This could lead to a denial of service, consuming excessive CPU and memory resources on the affected system.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
keystonePyPI | < 8.0.0a0 | 8.0.0a0 |
Affected products
4cpe:2.3:a:openstack:keystone:*:*:*:*:*:*:*:*+ 3 more
- cpe:2.3:a:openstack:keystone:*:*:*:*:*:*:*:*range: >=2012.1,<=2012.1.3
- cpe:2.3:a:openstack:keystone:2013.1:milestone1:*:*:*:*:*:*
- cpe:2.3:a:openstack:keystone:2013.1:milestone2:*:*:*:*:*:*
- cpe:2.3:a:openstack:keystone:2013.1:milestone3:*:*:*:*:*:*
Patches
282c87e5638ebMerge "Add size validations for /tokens." into stable/folsom
4 files changed · +118 −0
keystone/config.py+3 −0 modified@@ -117,6 +117,9 @@ def register_cli_int(*args, **kw): register_str('public_port', default=5000) register_str('onready') register_str('auth_admin_prefix', default='') +register_int('max_param_size', default=64) +# we allow tokens to be a bit larger to accomidate PKI +register_int('max_token_size', default=8192) #ssl options register_bool('enable', group='ssl', default=False)
keystone/exception.py+13 −0 modified@@ -51,6 +51,19 @@ class ValidationError(Error): title = 'Bad Request' +class ValidationSizeError(Error): + """Request attribute %(attribute)s must be less than or equal to %(size)i. + + The server could not comply with the request because the attribute + size is invalid (too large). + + The client is assumed to be in error. + + """ + code = 400 + title = 'Bad Request' + + class Unauthorized(Error): """The request you have made requires authentication.""" code = 401
keystone/service.py+27 −0 modified@@ -22,6 +22,7 @@ from keystone import catalog from keystone.common import cms from keystone.common import logging +from keystone.common import utils from keystone.common import wsgi from keystone import exception from keystone import identity @@ -31,6 +32,8 @@ LOG = logging.getLogger(__name__) +MAX_PARAM_SIZE = config.CONF.max_param_size +MAX_TOKEN_SIZE = config.CONF.max_token_size class AdminRouter(wsgi.ComposingRouter): @@ -288,9 +291,23 @@ def authenticate(self, context, auth=None): if 'passwordCredentials' in auth: user_id = auth['passwordCredentials'].get('userId', None) + if user_id and len(user_id) > MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='userId', + size=MAX_PARAM_SIZE) username = auth['passwordCredentials'].get('username', '') + if len(username) > MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='username', + size=MAX_PARAM_SIZE) password = auth['passwordCredentials'].get('password', '') + max_pw_size = utils.MAX_PASSWORD_LENGTH + if len(password) > max_pw_size: + raise exception.ValidationSizeError(attribute='password', + size=max_pw_size) + tenant_name = auth.get('tenantName', None) + if tenant_name and len(tenant_name) > MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='tenantName', + size=MAX_PARAM_SIZE) if username: try: @@ -302,6 +319,9 @@ def authenticate(self, context, auth=None): # more compat tenant_id = auth.get('tenantId', None) + if tenant_id and len(tenant_id) > MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='tenantId', + size=MAX_PARAM_SIZE) if tenant_name: try: tenant_ref = self.identity_api.get_tenant_by_name( @@ -342,7 +362,14 @@ def authenticate(self, context, auth=None): catalog_ref = {} elif 'token' in auth: old_token = auth['token'].get('id', None) + + if len(old_token) > MAX_TOKEN_SIZE: + raise exception.ValidationSizeError(attribute='token', + size=MAX_TOKEN_SIZE) tenant_name = auth.get('tenantName') + if tenant_name and len(tenant_name) > MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='tenantName', + size=MAX_PARAM_SIZE) try: old_token_ref = self.token_api.get_token(context=context,
tests/test_service.py+75 −0 modified@@ -17,6 +17,7 @@ import default_fixtures from keystone import config +from keystone import exception from keystone import service from keystone import test from keystone.identity.backends import kvs as kvs_identity @@ -25,6 +26,31 @@ CONF = config.CONF +def _build_user_auth(token=None, user_id=None, username=None, + password=None, tenant_id=None, tenant_name=None): + """Build auth dictionary. + + It will create an auth dictionary based on all the arguments + that it receives. + """ + auth_json = {} + if token is not None: + auth_json['token'] = token + if username or password: + auth_json['passwordCredentials'] = {} + if username is not None: + auth_json['passwordCredentials']['username'] = username + if user_id is not None: + auth_json['passwordCredentials']['userId'] = user_id + if password is not None: + auth_json['passwordCredentials']['password'] = password + if tenant_name is not None: + auth_json['tenantName'] = tenant_name + if tenant_id is not None: + auth_json['tenantId'] = tenant_id + return auth_json + + class TokenExpirationTest(test.TestCase): def setUp(self): super(TokenExpirationTest, self).setUp() @@ -75,3 +101,52 @@ def _maintain_token_expiration(self): def test_maintain_uuid_token_expiration(self): self.opt_in_group('signing', token_format='UUID') self._maintain_token_expiration() + + +class AuthTest(test.TestCase): + def setUp(self): + super(AuthTest, self).setUp() + + CONF.identity.driver = 'keystone.identity.backends.kvs.Identity' + self.load_backends() + self.load_fixtures(default_fixtures) + self.api = service.TokenController() + + def test_authenticate_user_id_too_large(self): + """Verify sending large 'userId' raises the right exception.""" + body_dict = _build_user_auth(user_id='0' * 65, username='FOO', + password='foo2') + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_username_too_large(self): + """Verify sending large 'username' raises the right exception.""" + body_dict = _build_user_auth(username='0' * 65, password='foo2') + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_tenant_id_too_large(self): + """Verify sending large 'tenantId' raises the right exception.""" + body_dict = _build_user_auth(username='FOO', password='foo2', + tenant_id='0' * 65) + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_tenant_name_too_large(self): + """Verify sending large 'tenantName' raises the right exception.""" + body_dict = _build_user_auth(username='FOO', password='foo2', + tenant_name='0' * 65) + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_token_too_large(self): + """Verify sending large 'token' raises the right exception.""" + body_dict = _build_user_auth(token={'id': '0' * 8193}) + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_password_too_large(self): + """Verify sending large 'password' raises the right exception.""" + body_dict = _build_user_auth(username='FOO', password='0' * 8193) + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict)
7691276b869aLimit the size of HTTP requests.
6 files changed · +127 −5
etc/keystone.conf.sample+8 −5 modified@@ -186,6 +186,9 @@ paste.filter_factory = keystone.contrib.s3:S3Extension.factory [filter:url_normalize] paste.filter_factory = keystone.middleware:NormalizingFilter.factory +[filter:sizelimit] +paste.filter_factory = keystone.middleware:RequestBodySizeLimiter.factory + [filter:stats_monitoring] paste.filter_factory = keystone.contrib.stats:StatsMiddleware.factory @@ -202,13 +205,13 @@ paste.app_factory = keystone.service:v3_app_factory paste.app_factory = keystone.service:admin_app_factory [pipeline:public_api] -pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug ec2_extension user_crud_extension public_service +pipeline = sizelimit stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug ec2_extension user_crud_extension public_service [pipeline:admin_api] -pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension crud_extension admin_service +pipeline = sizelimit stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension crud_extension admin_service [pipeline:api_v3] -pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension service_v3 +pipeline = sizelimit stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension service_v3 [app:public_version_service] paste.app_factory = keystone.service:public_version_app_factory @@ -217,10 +220,10 @@ paste.app_factory = keystone.service:public_version_app_factory paste.app_factory = keystone.service:admin_version_app_factory [pipeline:public_version_api] -pipeline = stats_monitoring url_normalize xml_body public_version_service +pipeline = sizelimit stats_monitoring url_normalize xml_body public_version_service [pipeline:admin_version_api] -pipeline = stats_monitoring url_normalize xml_body admin_version_service +pipeline = sizelimit stats_monitoring url_normalize xml_body admin_version_service [composite:main] use = egg:Paste#urlmap
keystone/common/utils.py+34 −0 modified@@ -311,3 +311,37 @@ def setup_remote_pydev_debug(): except: LOG.exception(_(error_msg)) raise + + +class LimitingReader(object): + """Reader to limit the size of an incoming request.""" + def __init__(self, data, limit): + """ + :param data: Underlying data object + :param limit: maximum number of bytes the reader should allow + """ + self.data = data + self.limit = limit + self.bytes_read = 0 + + def __iter__(self): + for chunk in self.data: + self.bytes_read += len(chunk) + if self.bytes_read > self.limit: + raise exception.RequestTooLarge() + else: + yield chunk + + def read(self, i): + result = self.data.read(i) + self.bytes_read += len(result) + if self.bytes_read > self.limit: + raise exception.RequestTooLarge() + return result + + def read(self): + result = self.data.read() + self.bytes_read += len(result) + if self.bytes_read > self.limit: + raise exception.RequestTooLarge() + return result
keystone/config.py+2 −0 modified@@ -137,6 +137,8 @@ def register_cli_int(*args, **kw): register_str('auth_admin_prefix', default='') register_str('policy_file', default='policy.json') register_str('policy_default_rule', default=None) +#default max request size is 112k +register_int('max_request_body_size', default=114688) #ssl options register_bool('enable', group='ssl', default=False)
keystone/exception.py+6 −0 modified@@ -173,6 +173,12 @@ class Conflict(Error): title = 'Conflict' +class RequestTooLarge(Error): + """Request is too large.""" + code = 413 + title = 'Request is too large.' + + class UnexpectedError(Error): """An unexpected error prevented the server from fulfilling your request.
keystone/middleware/core.py+21 −0 modified@@ -14,7 +14,10 @@ # License for the specific language governing permissions and limitations # under the License. +import webob.dec + from keystone.common import serializer +from keystone.common import utils from keystone.common import wsgi from keystone import config from keystone import exception @@ -164,3 +167,21 @@ def process_request(self, request): # Rewrites path to root if no path is given. elif not request.environ['PATH_INFO']: request.environ['PATH_INFO'] = '/' + + +class RequestBodySizeLimiter(wsgi.Middleware): + """Limit the size of an incoming request.""" + + def __init__(self, *args, **kwargs): + super(RequestBodySizeLimiter, self).__init__(*args, **kwargs) + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + + if req.content_length > CONF.max_request_body_size: + raise exception.RequestTooLarge() + if req.content_length is None and req.is_body_readable: + limiter = utils.LimitingReader(req.body_file, + CONF.max_request_body_size) + req.body_file = limiter + return self.application
tests/test_sizelimit.py+56 −0 added@@ -0,0 +1,56 @@ +# Copyright (c) 2013 OpenStack, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import webob + +from keystone import config +from keystone import exception +from keystone import middleware +from keystone import test + +CONF = config.CONF +MAX_REQUEST_BODY_SIZE = CONF.max_request_body_size + + +class TestRequestBodySizeLimiter(test.TestCase): + + def setUp(self): + super(TestRequestBodySizeLimiter, self).setUp() + + @webob.dec.wsgify() + def fake_app(req): + return webob.Response(req.body) + + self.middleware = middleware.RequestBodySizeLimiter(fake_app) + self.request = webob.Request.blank('/', method='POST') + + def test_content_length_acceptable(self): + self.request.headers['Content-Length'] = MAX_REQUEST_BODY_SIZE + self.request.body = "0" * MAX_REQUEST_BODY_SIZE + response = self.request.get_response(self.middleware) + self.assertEqual(response.status_int, 200) + + def test_content_length_too_large(self): + self.request.headers['Content-Length'] = MAX_REQUEST_BODY_SIZE + 1 + self.request.body = "0" * (MAX_REQUEST_BODY_SIZE + 1) + self.assertRaises(exception.RequestTooLarge, + self.request.get_response, + self.middleware) + + def test_request_too_large_no_content_length(self): + self.request.body = "0" * (MAX_REQUEST_BODY_SIZE + 1) + self.request.headers['Content-Length'] = None + self.assertRaises(exception.RequestTooLarge, + self.request.get_response, + self.middleware)
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
9- launchpad.net/keystone/grizzly/2013.1nvdPatchThird Party AdvisoryWEB
- rhn.redhat.com/errata/RHSA-2013-0708.htmlnvdThird Party AdvisoryWEB
- bugs.launchpad.net/keystone/+bug/1099025nvdThird Party AdvisoryWEB
- bugzilla.redhat.com/show_bug.cginvdThird Party AdvisoryWEB
- github.com/advisories/GHSA-4ppj-4p4v-jf4pghsaADVISORY
- github.com/openstack/keystone/commit/7691276b869a86c2b75631d5bede9f61e030d9d8nvdThird Party AdvisoryWEB
- github.com/openstack/keystone/commit/82c87e5638ebaf9f166a9b07a0155291276d6fdcnvdThird Party AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2013-0270ghsaADVISORY
- access.redhat.com/security/cve/CVE-2013-0270nvdWEB
News mentions
0No linked articles in our index yet.