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

Commit 70af798

Browse files
committed
Hash token before storing it in Swift
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
1 parent 54ac16a commit 70af798

File tree

3 files changed

+59
-5
lines changed

3 files changed

+59
-5
lines changed

Diff for: swauth/middleware.py

+22-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import base64
1717
from hashlib import sha1
18+
from hashlib import sha512
1819
import hmac
1920
from httplib import HTTPConnection
2021
from httplib import HTTPSConnection
@@ -50,6 +51,8 @@
5051
from swift.common.utils import cache_from_env
5152
from swift.common.utils import get_logger
5253
from swift.common.utils import get_remote_client
54+
from swift.common.utils import HASH_PATH_PREFIX
55+
from swift.common.utils import HASH_PATH_SUFFIX
5356
from swift.common.utils import split_path
5457
from swift.common.utils import TRUE_VALUES
5558
from swift.common.utils import urlparse
@@ -289,6 +292,15 @@ def __call__(self, env, start_response):
289292
env['swift.clean_acl'] = clean_acl
290293
return self.app(env, start_response)
291294

295+
def _get_concealed_token(self, token):
296+
"""Returns hashed token to be used as object name in Swift.
297+
298+
Tokens are stored in auth account but object names are visible in Swift
299+
logs. Object names are hashed from token.
300+
"""
301+
enc_key = "%s:%s:%s" % (HASH_PATH_PREFIX, token, HASH_PATH_SUFFIX)
302+
return sha512(enc_key).hexdigest()
303+
292304
def get_groups(self, env, token):
293305
"""Get groups for the given token.
294306
@@ -397,8 +409,9 @@ def get_groups(self, env, token):
397409
memcache_key, (time() + expires_from_now, groups),
398410
time=expires_from_now)
399411
else:
412+
object_name = self._get_concealed_token(token)
400413
path = quote('/v1/%s/.token_%s/%s' %
401-
(self.auth_account, token[-1], token))
414+
(self.auth_account, object_name[-1], object_name))
402415
resp = self.make_pre_authed_request(
403416
env, 'GET', path).get_response(self.app)
404417
if resp.status_int // 100 != 2:
@@ -1168,8 +1181,9 @@ def handle_delete_user(self, req):
11681181
(path, resp.status))
11691182
candidate_token = resp.headers.get('x-object-meta-auth-token')
11701183
if candidate_token:
1184+
object_name = self._get_concealed_token(candidate_token)
11711185
path = quote('/v1/%s/.token_%s/%s' %
1172-
(self.auth_account, candidate_token[-1], candidate_token))
1186+
(self.auth_account, object_name[-1], object_name))
11731187
resp = self.make_pre_authed_request(
11741188
req.environ, 'DELETE', path).get_response(self.app)
11751189
if resp.status_int // 100 != 2 and resp.status_int != 404:
@@ -1318,8 +1332,9 @@ def handle_get_token(self, req):
13181332
expires = None
13191333
candidate_token = resp.headers.get('x-object-meta-auth-token')
13201334
if candidate_token:
1335+
object_name = self._get_concealed_token(candidate_token)
13211336
path = quote('/v1/%s/.token_%s/%s' %
1322-
(self.auth_account, candidate_token[-1], candidate_token))
1337+
(self.auth_account, object_name[-1], object_name))
13231338
delete_token = False
13241339
try:
13251340
if req.headers.get('x-auth-new-token', 'false').lower() in \
@@ -1362,8 +1377,9 @@ def handle_get_token(self, req):
13621377
# Generate new token
13631378
token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
13641379
# Save token info
1380+
object_name = self._get_concealed_token(token)
13651381
path = quote('/v1/%s/.token_%s/%s' %
1366-
(self.auth_account, token[-1], token))
1382+
(self.auth_account, object_name[-1], object_name))
13671383
try:
13681384
token_life = min(
13691385
int(req.headers.get('x-auth-token-lifetime',
@@ -1439,8 +1455,9 @@ def handle_validate_token(self, req):
14391455
if expires < time():
14401456
groups = None
14411457
if not groups:
1458+
object_name = self._get_concealed_token(token)
14421459
path = quote('/v1/%s/.token_%s/%s' %
1443-
(self.auth_account, token[-1], token))
1460+
(self.auth_account, object_name[-1], object_name))
14441461
resp = self.make_pre_authed_request(
14451462
req.environ, 'GET', path).get_response(self.app)
14461463
if resp.status_int // 100 != 2:

Diff for: test/unit/test_authtypes.py

+1
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,6 @@ def test_sha512_invalid_match(self):
202202
match = self.auth_encoder.match('keystring2', creds, **creds_dict)
203203
self.assertEqual(match, False)
204204

205+
205206
if __name__ == '__main__':
206207
unittest.main()

Diff for: test/unit/test_middleware.py

+36
Original file line numberDiff line numberDiff line change
@@ -4125,6 +4125,42 @@ def test_s3_only_hash_passed_to_hmac(self):
41254125
# Assert that string passed to hmac.new is only the hash
41264126
self.assertEqual(mock_hmac_new.call_args[0][0], key_hash)
41274127

4128+
def test_get_concealed_token(self):
4129+
auth.HASH_PATH_PREFIX = 'start'
4130+
auth.HASH_PATH_SUFFIX = 'end'
4131+
token = 'token'
4132+
4133+
# Check sha512 of "start:token:end"
4134+
hashed_token = self.test_auth._get_concealed_token(token)
4135+
self.assertEqual(hashed_token,
4136+
'cb320540b0b4c69eb83de2ffb80714cb6766e2d06b5579d1a35a9c4c3fb62'
4137+
'981ec50bcc3fb94521133e69a87d1efcb83efd78f35a06b6375e410201476'
4138+
'0722f6')
4139+
4140+
# Check sha512 of "start:token2:end"
4141+
token = 'token2'
4142+
hashed_token = self.test_auth._get_concealed_token(token)
4143+
self.assertEqual(hashed_token,
4144+
'ca400a6f884c168357f6af0609fda66aecd5aa613147167487495dd9f39fd'
4145+
'8a77288568e65857294f01e398d7f14328e855f18517ccf94185d849e7f34'
4146+
'f4259d')
4147+
4148+
# Check sha512 of "start2:token2:end"
4149+
auth.HASH_PATH_PREFIX = 'start2'
4150+
hashed_token = self.test_auth._get_concealed_token(token)
4151+
self.assertEqual(hashed_token,
4152+
'ad594a69f44dd6e0aad54e360b01f15bd4833ccb4dcd9116d7aba0c25fb95'
4153+
'670155b8cc7175def7aeeb4624a0f2bb7da5f0b204a4680ea7947d3d6a045'
4154+
'22bdde')
4155+
4156+
# Check sha512 of "start2:token2:end2"
4157+
auth.HASH_PATH_SUFFIX = 'end2'
4158+
hashed_token = self.test_auth._get_concealed_token(token)
4159+
self.assertEqual(hashed_token,
4160+
'446af2473ad6b28319a0fe02719a9d715b9941d12e0709851aedb4f53b890'
4161+
'693e7f1328e68d870fe114f35f4ed9648b16a5013182db50d3d1f79a660f2'
4162+
'0e078e')
4163+
41284164

41294165
if __name__ == '__main__':
41304166
unittest.main()

0 commit comments

Comments
 (0)