Skip to content

Commit eb39516

Browse files
[CVE-2024-6866] Case Sensitive Request Path Matching (#390)
* [CVE-2024-6866] Case Sensitive Request Path Matching * Update flask_cors/core.py --------- Co-authored-by: Cory Dolphin <corydolphin@gmail.com>
1 parent 5da9be4 commit eb39516

3 files changed

Lines changed: 36 additions & 23 deletions

File tree

flask_cors/core.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ def get_cors_origins(options, request_origin):
121121
if wildcard and options.get("send_wildcard"):
122122
LOG.debug("Allowed origins are set to '*'. Sending wildcard CORS header.")
123123
return ["*"]
124-
# If the value of the Origin header is a case-sensitive match
125-
# for any of the values in list of origins
126-
elif try_match_any(request_origin, origins):
124+
# If the value of the Origin header is a case-insensitive match
125+
# for any of the values in list of origins.
126+
# NOTE: Per RFC 1035 and RFC 4343 schemes and hostnames are case insensitive.
127+
elif try_match_any_pattern(request_origin, origins, caseSensitive=False):
127128
LOG.debug(
128129
"The request's Origin header matches. Sending CORS headers.",
129130
)
@@ -164,7 +165,7 @@ def get_allow_headers(options, acl_request_headers):
164165
request_headers = [h.strip() for h in acl_request_headers.split(",")]
165166

166167
# any header that matches in the allow_headers
167-
matching_headers = filter(lambda h: try_match_any(h, options.get("allow_headers")), request_headers)
168+
matching_headers = filter(lambda h: try_match_any_pattern(h, options.get("allow_headers"), caseSensitive=False), request_headers)
168169

169170
return ", ".join(sorted(matching_headers))
170171

@@ -277,22 +278,31 @@ def re_fix(reg):
277278
return r".*" if reg == r"*" else reg
278279

279280

280-
def try_match_any(inst, patterns):
281-
return any(try_match(inst, pattern) for pattern in patterns)
281+
def try_match_any_pattern(inst, patterns, caseSensitive=True):
282+
return any(try_match_pattern(inst, pattern, caseSensitive) for pattern in patterns)
282283

283-
284-
def try_match(request_origin, maybe_regex):
285-
"""Safely attempts to match a pattern or string to a request origin."""
286-
if isinstance(maybe_regex, RegexObject):
287-
return re.match(maybe_regex, request_origin)
288-
elif probably_regex(maybe_regex):
289-
return re.match(maybe_regex, request_origin, flags=re.IGNORECASE)
290-
else:
284+
def try_match_pattern(value, pattern, caseSensitive=True):
285+
"""
286+
Safely attempts to match a pattern or string to a value. This
287+
function can be used to match request origins, headers, or paths.
288+
The value of caseSensitive should be set in accordance to the
289+
data being compared e.g. origins and headers are case insensitive
290+
whereas paths are case-sensitive
291+
"""
292+
if isinstance(pattern, RegexObject):
293+
return re.match(pattern, value)
294+
if probably_regex(pattern):
295+
flags = 0 if caseSensitive else re.IGNORECASE
291296
try:
292-
return request_origin.lower() == maybe_regex.lower()
293-
except AttributeError:
294-
return request_origin == maybe_regex
295-
297+
return re.match(pattern, value, flags=flags)
298+
except re.error:
299+
return False
300+
try:
301+
v = str(value)
302+
p = str(pattern)
303+
return v == p if caseSensitive else v.casefold() == p.casefold()
304+
except Exception:
305+
return value == pattern
296306

297307
def get_cors_options(appInstance, *dicts):
298308
"""

flask_cors/extension.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from flask import request
55

6-
from .core import ACL_ORIGIN, get_cors_options, get_regexp_pattern, parse_resources, set_cors_headers, try_match
6+
from .core import ACL_ORIGIN, get_cors_options, get_regexp_pattern, parse_resources, set_cors_headers, try_match_pattern
77

88
LOG = logging.getLogger(__name__)
99

@@ -190,7 +190,7 @@ def cors_after_request(resp):
190190
return resp
191191
normalized_path = unquote_plus(request.path)
192192
for res_regex, res_options in resources:
193-
if try_match(normalized_path, res_regex):
193+
if try_match_pattern(normalized_path, res_regex, caseSensitive=True):
194194
LOG.debug(
195195
"Request to '%r' matches CORS resource '%s'. Using options: %s",
196196
request.path,

tests/core/helper_tests.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717

1818

1919
class InternalsTestCase(unittest.TestCase):
20-
def test_try_match(self):
21-
self.assertFalse(try_match('www.com/foo', 'www.com/fo'))
22-
self.assertTrue(try_match('www.com/foo', 'www.com/fo*'))
20+
def test_try_match_pattern(self):
21+
self.assertFalse(try_match_pattern('www.com/foo', 'www.com/fo', caseSensitive=True))
22+
self.assertTrue(try_match_pattern('www.com/foo', 'www.com/fo*', caseSensitive=True))
23+
self.assertTrue(try_match_pattern('www.com', 'WwW.CoM', caseSensitive=False))
24+
self.assertTrue(try_match_pattern('/foo', '/fo*', caseSensitive=True))
25+
self.assertFalse(try_match_pattern('/foo', '/Fo*', caseSensitive=True))
2326

2427
def test_flexible_str_str(self):
2528
self.assertEqual(flexible_str('Bar, Foo, Qux'), 'Bar, Foo, Qux')

0 commit comments

Comments
 (0)