Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Commit

Permalink
Hash token before storing it in Swift
Browse files Browse the repository at this point in the history
Swauth uses token value as object name. Object names are logged in proxy
and object servers. Anybody with access to proxy/object server logs can
see token values. Attacker can use this token to access user's data in
Swift store. Instead of token, hashed token (with HASH_PATH_PREFIX and
HASH_PATH_SUFFIX) is used as object name now.

WARNING: In deployments without memcached this patch logs out all users
because tokens became invalid.

CVE-2017-16613

SecurityImpact
Closes-Bug: #1655781
Change-Id: I0d01e8e95400c82ef25f98e2d269532e83233c2c
  • Loading branch information
ctvera committed Nov 21, 2017
1 parent 54ac16a commit 70af798
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
27 changes: 22 additions & 5 deletions swauth/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import base64
from hashlib import sha1
from hashlib import sha512
import hmac
from httplib import HTTPConnection
from httplib import HTTPSConnection
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 \
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions test/unit/test_authtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
36 changes: 36 additions & 0 deletions test/unit/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

0 comments on commit 70af798

Please sign in to comment.