|
1 This upstream patch addresses CVE-2015-5223 in swift. It may be removed |
|
2 when swift 2.4.0 or later is integrated. |
|
3 |
|
4 From 0694e1911d10a18075ff99462c96781372422b2c Mon Sep 17 00:00:00 2001 |
|
5 From: Clay Gerrard <[email protected]> |
|
6 Date: Thu, 23 Jul 2015 22:36:21 -0700 |
|
7 Subject: Disallow unsafe tempurl operations to point to unauthorized data |
|
8 |
|
9 Do not allow PUT tempurls to create pointers to other data. Specifically |
|
10 disallow the creation of DLO object manifests by returning an error if a |
|
11 non-safe tempurl request includes an X-Object-Manifest header regardless of |
|
12 the value of the header. |
|
13 |
|
14 This prevents discoverability attacks which can use any PUT tempurl to probe |
|
15 for private data by creating a DLO object manifest and then using the PUT |
|
16 tempurl to head the object which would 404 if the prefix does not match any |
|
17 object data or form a valid DLO HEAD response if it does. |
|
18 |
|
19 This also prevents a tricky and potentially unexpected consequence of PUT |
|
20 tempurls which would make it unsafe to allow a user to download objects |
|
21 created by tempurl (even if they just created them) because the result of |
|
22 reading the object created via tempurl may not be the data which was uploaded. |
|
23 |
|
24 [CVE-2015-5223] |
|
25 |
|
26 Co-Authored-By: Kota Tsuyuzaki <[email protected]> |
|
27 |
|
28 Closes-Bug: 1453948 |
|
29 |
|
30 Change-Id: I91161dfb0f089c3990aca1b4255b520299ef73c8 |
|
31 |
|
32 --- swift-2.3.0/swift/common/middleware/tempurl.py.~1~ 2015-04-30 09:57:42.000000000 -0400 |
|
33 +++ swift-2.3.0/swift/common/middleware/tempurl.py 2015-11-03 17:11:02.364113024 -0500 |
|
34 @@ -122,11 +122,13 @@ |
|
35 from urlparse import parse_qs |
|
36 |
|
37 from swift.proxy.controllers.base import get_account_info, get_container_info |
|
38 -from swift.common.swob import HeaderKeyDict, HTTPUnauthorized |
|
39 +from swift.common.swob import HeaderKeyDict, HTTPUnauthorized, HTTPBadRequest |
|
40 from swift.common.utils import split_path, get_valid_utf8_str, \ |
|
41 register_swift_info, get_hmac, streq_const_time, quote |
|
42 |
|
43 |
|
44 +DISALLOWED_INCOMING_HEADERS = 'x-object-manifest' |
|
45 + |
|
46 #: Default headers to remove from incoming requests. Simply a whitespace |
|
47 #: delimited list of header names and names can optionally end with '*' to |
|
48 #: indicate a prefix match. DEFAULT_INCOMING_ALLOW_HEADERS is a list of |
|
49 @@ -230,6 +232,10 @@ |
|
50 #: The methods allowed with Temp URLs. |
|
51 self.methods = methods |
|
52 |
|
53 + self.disallowed_headers = set( |
|
54 + 'HTTP_' + h.upper().replace('-', '_') |
|
55 + for h in DISALLOWED_INCOMING_HEADERS.split()) |
|
56 + |
|
57 headers = DEFAULT_INCOMING_REMOVE_HEADERS |
|
58 if 'incoming_remove_headers' in conf: |
|
59 headers = conf['incoming_remove_headers'] |
|
60 @@ -323,6 +329,13 @@ |
|
61 for hmac in hmac_vals) |
|
62 if not is_valid_hmac: |
|
63 return self._invalid(env, start_response) |
|
64 + # disallowed headers prevent accidently allowing upload of a pointer |
|
65 + # to data that the PUT tempurl would not otherwise allow access for. |
|
66 + # It should be safe to provide a GET tempurl for data that an |
|
67 + # untrusted client just uploaded with a PUT tempurl. |
|
68 + resp = self._clean_disallowed_headers(env, start_response) |
|
69 + if resp: |
|
70 + return resp |
|
71 self._clean_incoming_headers(env) |
|
72 env['swift.authorize'] = lambda req: None |
|
73 env['swift.authorize_override'] = True |
|
74 @@ -465,6 +478,22 @@ |
|
75 body = '401 Unauthorized: Temp URL invalid\n' |
|
76 return HTTPUnauthorized(body=body)(env, start_response) |
|
77 |
|
78 + def _clean_disallowed_headers(self, env, start_response): |
|
79 + """ |
|
80 + Validate the absense of disallowed headers for "unsafe" operations. |
|
81 + |
|
82 + :returns: None for safe operations or swob.HTTPBadResponse if the |
|
83 + request includes disallowed headers. |
|
84 + """ |
|
85 + if env['REQUEST_METHOD'] in ('GET', 'HEAD', 'OPTIONS'): |
|
86 + return |
|
87 + for h in env: |
|
88 + if h in self.disallowed_headers: |
|
89 + return HTTPBadRequest( |
|
90 + body='The header %r is not allowed in this tempurl' % |
|
91 + h[len('HTTP_'):].title().replace('_', '-'))( |
|
92 + env, start_response) |
|
93 + |
|
94 def _clean_incoming_headers(self, env): |
|
95 """ |
|
96 Removes any headers from the WSGI environment as per the |
|
97 --- swift-2.3.0/test/functional/tests.py.~1~ 2015-04-30 09:57:42.000000000 -0400 |
|
98 +++ swift-2.3.0/test/functional/tests.py 2015-11-03 15:27:42.202245458 -0500 |
|
99 @@ -2732,6 +2732,42 @@ |
|
100 self.assert_(new_obj.info(parms=put_parms, |
|
101 cfg={'no_auth_token': True})) |
|
102 |
|
103 + def test_PUT_manifest_access(self): |
|
104 + new_obj = self.env.container.file(Utils.create_name()) |
|
105 + |
|
106 + # give out a signature which allows a PUT to new_obj |
|
107 + expires = int(time.time()) + 86400 |
|
108 + sig = self.tempurl_sig( |
|
109 + 'PUT', expires, self.env.conn.make_path(new_obj.path), |
|
110 + self.env.tempurl_key) |
|
111 + put_parms = {'temp_url_sig': sig, |
|
112 + 'temp_url_expires': str(expires)} |
|
113 + |
|
114 + # try to create manifest pointing to some random container |
|
115 + try: |
|
116 + new_obj.write('', { |
|
117 + 'x-object-manifest': '%s/foo' % 'some_random_container' |
|
118 + }, parms=put_parms, cfg={'no_auth_token': True}) |
|
119 + except ResponseError as e: |
|
120 + self.assertEqual(e.status, 400) |
|
121 + else: |
|
122 + self.fail('request did not error') |
|
123 + |
|
124 + # create some other container |
|
125 + other_container = self.env.account.container(Utils.create_name()) |
|
126 + if not other_container.create(): |
|
127 + raise ResponseError(self.conn.response) |
|
128 + |
|
129 + # try to create manifest pointing to new container |
|
130 + try: |
|
131 + new_obj.write('', { |
|
132 + 'x-object-manifest': '%s/foo' % other_container |
|
133 + }, parms=put_parms, cfg={'no_auth_token': True}) |
|
134 + except ResponseError as e: |
|
135 + self.assertEqual(e.status, 400) |
|
136 + else: |
|
137 + self.fail('request did not error') |
|
138 + |
|
139 def test_HEAD(self): |
|
140 expires = int(time.time()) + 86400 |
|
141 sig = self.tempurl_sig( |
|
142 --- swift-2.3.0/test/unit/common/middleware/test_tempurl.py.~1~ 2015-04-30 09:57:42.000000000 -0400 |
|
143 +++ swift-2.3.0/test/unit/common/middleware/test_tempurl.py 2015-11-03 15:27:42.202552552 -0500 |
|
144 @@ -649,6 +649,25 @@ |
|
145 self.assertTrue('Temp URL invalid' in resp.body) |
|
146 self.assertTrue('Www-Authenticate' in resp.headers) |
|
147 |
|
148 + def test_disallowed_header_object_manifest(self): |
|
149 + self.tempurl = tempurl.filter_factory({})(self.auth) |
|
150 + method = 'PUT' |
|
151 + expires = int(time() + 86400) |
|
152 + path = '/v1/a/c/o' |
|
153 + key = 'abc' |
|
154 + hmac_body = '%s\n%s\n%s' % (method, expires, path) |
|
155 + sig = hmac.new(key, hmac_body, sha1).hexdigest() |
|
156 + req = self._make_request( |
|
157 + path, method='PUT', keys=[key], |
|
158 + headers={'x-object-manifest': 'private/secret'}, |
|
159 + environ={'QUERY_STRING': 'temp_url_sig=%s&temp_url_expires=%s' % ( |
|
160 + sig, expires)}) |
|
161 + resp = req.get_response(self.tempurl) |
|
162 + self.assertEquals(resp.status_int, 400) |
|
163 + self.assertTrue('header' in resp.body) |
|
164 + self.assertTrue('not allowed' in resp.body) |
|
165 + self.assertTrue('X-Object-Manifest' in resp.body) |
|
166 + |
|
167 def test_removed_incoming_header(self): |
|
168 self.tempurl = tempurl.filter_factory({ |
|
169 'incoming_remove_headers': 'x-remove-this'})(self.auth) |