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

PackageAffected versionsPatched versions
keystonePyPI
< 8.0.0a08.0.0a0

Affected products

4
  • cpe: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

2
82c87e5638eb

Merge "Add size validations for /tokens." into stable/folsom

https://github.com/openstack/keystoneJenkinsFeb 5, 2013via ghsa
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)
    
7691276b869a

Limit the size of HTTP requests.

https://github.com/openstack/keystoneDan PrinceJan 13, 2013via ghsa
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

News mentions

0

No linked articles in our index yet.