Case-Insensitive Path Matching in corydolphin/flask-cors
Description
corydolphin/flask-cors version 4.01 contains a vulnerability where the request path matching is case-insensitive due to the use of the try_match function, which is originally intended for matching hosts. This results in a mismatch because paths in URLs are case-sensitive, but the regex matching treats them as case-insensitive. This misconfiguration can lead to significant security vulnerabilities, allowing unauthorized origins to access paths meant to be restricted, resulting in data exposure and potential data leaks.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
flask-corsPyPI | < 6.0.0 | 6.0.0 |
Affected products
1- Range: unspecified
Patches
1eb39516a3c96[CVE-2024-6866] Case Sensitive Request Path Matching (#390)
3 files changed · +36 −23
flask_cors/core.py+28 −18 modified@@ -121,9 +121,10 @@ def get_cors_origins(options, request_origin): if wildcard and options.get("send_wildcard"): LOG.debug("Allowed origins are set to '*'. Sending wildcard CORS header.") return ["*"] - # If the value of the Origin header is a case-sensitive match - # for any of the values in list of origins - elif try_match_any(request_origin, origins): + # If the value of the Origin header is a case-insensitive match + # for any of the values in list of origins. + # NOTE: Per RFC 1035 and RFC 4343 schemes and hostnames are case insensitive. + elif try_match_any_pattern(request_origin, origins, caseSensitive=False): LOG.debug( "The request's Origin header matches. Sending CORS headers.", ) @@ -164,7 +165,7 @@ def get_allow_headers(options, acl_request_headers): request_headers = [h.strip() for h in acl_request_headers.split(",")] # any header that matches in the allow_headers - matching_headers = filter(lambda h: try_match_any(h, options.get("allow_headers")), request_headers) + matching_headers = filter(lambda h: try_match_any_pattern(h, options.get("allow_headers"), caseSensitive=False), request_headers) return ", ".join(sorted(matching_headers)) @@ -277,22 +278,31 @@ def re_fix(reg): return r".*" if reg == r"*" else reg -def try_match_any(inst, patterns): - return any(try_match(inst, pattern) for pattern in patterns) +def try_match_any_pattern(inst, patterns, caseSensitive=True): + return any(try_match_pattern(inst, pattern, caseSensitive) for pattern in patterns) - -def try_match(request_origin, maybe_regex): - """Safely attempts to match a pattern or string to a request origin.""" - if isinstance(maybe_regex, RegexObject): - return re.match(maybe_regex, request_origin) - elif probably_regex(maybe_regex): - return re.match(maybe_regex, request_origin, flags=re.IGNORECASE) - else: +def try_match_pattern(value, pattern, caseSensitive=True): + """ + Safely attempts to match a pattern or string to a value. This + function can be used to match request origins, headers, or paths. + The value of caseSensitive should be set in accordance to the + data being compared e.g. origins and headers are case insensitive + whereas paths are case-sensitive + """ + if isinstance(pattern, RegexObject): + return re.match(pattern, value) + if probably_regex(pattern): + flags = 0 if caseSensitive else re.IGNORECASE try: - return request_origin.lower() == maybe_regex.lower() - except AttributeError: - return request_origin == maybe_regex - + return re.match(pattern, value, flags=flags) + except re.error: + return False + try: + v = str(value) + p = str(pattern) + return v == p if caseSensitive else v.casefold() == p.casefold() + except Exception: + return value == pattern def get_cors_options(appInstance, *dicts): """
flask_cors/extension.py+2 −2 modified@@ -3,7 +3,7 @@ from flask import request -from .core import ACL_ORIGIN, get_cors_options, get_regexp_pattern, parse_resources, set_cors_headers, try_match +from .core import ACL_ORIGIN, get_cors_options, get_regexp_pattern, parse_resources, set_cors_headers, try_match_pattern LOG = logging.getLogger(__name__) @@ -190,7 +190,7 @@ def cors_after_request(resp): return resp normalized_path = unquote_plus(request.path) for res_regex, res_options in resources: - if try_match(normalized_path, res_regex): + if try_match_pattern(normalized_path, res_regex, caseSensitive=True): LOG.debug( "Request to '%r' matches CORS resource '%s'. Using options: %s", request.path,
tests/core/helper_tests.py+6 −3 modified@@ -17,9 +17,12 @@ class InternalsTestCase(unittest.TestCase): - def test_try_match(self): - self.assertFalse(try_match('www.com/foo', 'www.com/fo')) - self.assertTrue(try_match('www.com/foo', 'www.com/fo*')) + def test_try_match_pattern(self): + self.assertFalse(try_match_pattern('www.com/foo', 'www.com/fo', caseSensitive=True)) + self.assertTrue(try_match_pattern('www.com/foo', 'www.com/fo*', caseSensitive=True)) + self.assertTrue(try_match_pattern('www.com', 'WwW.CoM', caseSensitive=False)) + self.assertTrue(try_match_pattern('/foo', '/fo*', caseSensitive=True)) + self.assertFalse(try_match_pattern('/foo', '/Fo*', caseSensitive=True)) def test_flexible_str_str(self): self.assertEqual(flexible_str('Bar, Foo, Qux'), 'Bar, Foo, Qux')
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
6- github.com/advisories/GHSA-43qf-4rqw-9q2gghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-6866ghsaADVISORY
- github.com/corydolphin/flask-cors/blob/4.0.1/flask_cors/extension.pyghsaWEB
- github.com/corydolphin/flask-cors/commit/eb39516a3c96b90d0ae5f51293972395ec3ef358ghsaWEB
- huntr.com/bounties/808c11af-faee-43a8-824b-b5ab4f62b9e6ghsaWEB
- lists.debian.org/debian-lts-announce/2025/05/msg00049.htmlghsaWEB
News mentions
0No linked articles in our index yet.