VYPR
Critical severity9.8NVD Advisory· Published Nov 21, 2017· Updated May 13, 2026

CVE-2017-16613

CVE-2017-16613

Description

An issue was discovered in middleware.py in OpenStack Swauth through 1.2.0 when used with OpenStack Swift through 2.15.1. The Swift object store and proxy server are saving (unhashed) tokens retrieved from the Swauth middleware authentication mechanism to a log file as part of a GET URI. This allows attackers to bypass authentication by inserting a token into an X-Auth-Token header of a new request. NOTE: github.com/openstack/swauth URLs do not mean that Swauth is maintained by an official OpenStack project team.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
swauthPyPI
< 1.3.01.3.0

Affected products

3

Patches

1
70af7986265a

Hash token before storing it in Swift

https://github.com/openstack/swauthPavel KvasnickaNov 21, 2017via ghsa
3 files changed · +59 5
  • swauth/middleware.py+22 5 modified
    @@ -15,6 +15,7 @@
     
     import base64
     from hashlib import sha1
    +from hashlib import sha512
     import hmac
     from httplib import HTTPConnection
     from httplib import HTTPSConnection
    @@ -50,6 +51,8 @@
     from swift.common.utils import cache_from_env
     from swift.common.utils import get_logger
     from swift.common.utils import get_remote_client
    +from swift.common.utils import HASH_PATH_PREFIX
    +from swift.common.utils import HASH_PATH_SUFFIX
     from swift.common.utils import split_path
     from swift.common.utils import TRUE_VALUES
     from swift.common.utils import urlparse
    @@ -289,6 +292,15 @@ def __call__(self, env, start_response):
                     env['swift.clean_acl'] = clean_acl
             return self.app(env, start_response)
     
    +    def _get_concealed_token(self, token):
    +        """Returns hashed token to be used as object name in Swift.
    +
    +        Tokens are stored in auth account but object names are visible in Swift
    +        logs. Object names are hashed from token.
    +        """
    +        enc_key = "%s:%s:%s" % (HASH_PATH_PREFIX, token, HASH_PATH_SUFFIX)
    +        return sha512(enc_key).hexdigest()
    +
         def get_groups(self, env, token):
             """Get groups for the given token.
     
    @@ -397,8 +409,9 @@ def get_groups(self, env, token):
                             memcache_key, (time() + expires_from_now, groups),
                             time=expires_from_now)
                 else:
    +                object_name = self._get_concealed_token(token)
                     path = quote('/v1/%s/.token_%s/%s' %
    -                             (self.auth_account, token[-1], token))
    +                             (self.auth_account, object_name[-1], object_name))
                     resp = self.make_pre_authed_request(
                         env, 'GET', path).get_response(self.app)
                     if resp.status_int // 100 != 2:
    @@ -1168,8 +1181,9 @@ def handle_delete_user(self, req):
                                 (path, resp.status))
             candidate_token = resp.headers.get('x-object-meta-auth-token')
             if candidate_token:
    +            object_name = self._get_concealed_token(candidate_token)
                 path = quote('/v1/%s/.token_%s/%s' %
    -                (self.auth_account, candidate_token[-1], candidate_token))
    +                (self.auth_account, object_name[-1], object_name))
                 resp = self.make_pre_authed_request(
                     req.environ, 'DELETE', path).get_response(self.app)
                 if resp.status_int // 100 != 2 and resp.status_int != 404:
    @@ -1318,8 +1332,9 @@ def handle_get_token(self, req):
             expires = None
             candidate_token = resp.headers.get('x-object-meta-auth-token')
             if candidate_token:
    +            object_name = self._get_concealed_token(candidate_token)
                 path = quote('/v1/%s/.token_%s/%s' %
    -                (self.auth_account, candidate_token[-1], candidate_token))
    +                (self.auth_account, object_name[-1], object_name))
                 delete_token = False
                 try:
                     if req.headers.get('x-auth-new-token', 'false').lower() in \
    @@ -1362,8 +1377,9 @@ def handle_get_token(self, req):
                 # Generate new token
                 token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
                 # Save token info
    +            object_name = self._get_concealed_token(token)
                 path = quote('/v1/%s/.token_%s/%s' %
    -                         (self.auth_account, token[-1], token))
    +                         (self.auth_account, object_name[-1], object_name))
                 try:
                     token_life = min(
                         int(req.headers.get('x-auth-token-lifetime',
    @@ -1439,8 +1455,9 @@ def handle_validate_token(self, req):
                     if expires < time():
                         groups = None
             if not groups:
    +            object_name = self._get_concealed_token(token)
                 path = quote('/v1/%s/.token_%s/%s' %
    -                         (self.auth_account, token[-1], token))
    +                         (self.auth_account, object_name[-1], object_name))
                 resp = self.make_pre_authed_request(
                     req.environ, 'GET', path).get_response(self.app)
                 if resp.status_int // 100 != 2:
    
  • test/unit/test_authtypes.py+1 0 modified
    @@ -202,5 +202,6 @@ def test_sha512_invalid_match(self):
             match = self.auth_encoder.match('keystring2', creds, **creds_dict)
             self.assertEqual(match, False)
     
    +
     if __name__ == '__main__':
         unittest.main()
    
  • test/unit/test_middleware.py+36 0 modified
    @@ -4125,6 +4125,42 @@ def test_s3_only_hash_passed_to_hmac(self):
             # Assert that string passed to hmac.new is only the hash
             self.assertEqual(mock_hmac_new.call_args[0][0], key_hash)
     
    +    def test_get_concealed_token(self):
    +        auth.HASH_PATH_PREFIX = 'start'
    +        auth.HASH_PATH_SUFFIX = 'end'
    +        token = 'token'
    +
    +        # Check sha512 of "start:token:end"
    +        hashed_token = self.test_auth._get_concealed_token(token)
    +        self.assertEqual(hashed_token,
    +                'cb320540b0b4c69eb83de2ffb80714cb6766e2d06b5579d1a35a9c4c3fb62'
    +                '981ec50bcc3fb94521133e69a87d1efcb83efd78f35a06b6375e410201476'
    +                '0722f6')
    +
    +        # Check sha512 of "start:token2:end"
    +        token = 'token2'
    +        hashed_token = self.test_auth._get_concealed_token(token)
    +        self.assertEqual(hashed_token,
    +                'ca400a6f884c168357f6af0609fda66aecd5aa613147167487495dd9f39fd'
    +                '8a77288568e65857294f01e398d7f14328e855f18517ccf94185d849e7f34'
    +                'f4259d')
    +
    +        # Check sha512 of "start2:token2:end"
    +        auth.HASH_PATH_PREFIX = 'start2'
    +        hashed_token = self.test_auth._get_concealed_token(token)
    +        self.assertEqual(hashed_token,
    +                'ad594a69f44dd6e0aad54e360b01f15bd4833ccb4dcd9116d7aba0c25fb95'
    +                '670155b8cc7175def7aeeb4624a0f2bb7da5f0b204a4680ea7947d3d6a045'
    +                '22bdde')
    +
    +        # Check sha512 of "start2:token2:end2"
    +        auth.HASH_PATH_SUFFIX = 'end2'
    +        hashed_token = self.test_auth._get_concealed_token(token)
    +        self.assertEqual(hashed_token,
    +                '446af2473ad6b28319a0fe02719a9d715b9941d12e0709851aedb4f53b890'
    +                '693e7f1328e68d870fe114f35f4ed9648b16a5013182db50d3d1f79a660f2'
    +                '0e078e')
    +
     
     if __name__ == '__main__':
         unittest.main()
    

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.