--- Begin Message ---
Package: python3-flask-cors
Version: 3.0.10-2.2~deb12u1
Severity: minor
Tags: patch
X-Debbugs-Cc: [email protected]
Dear Maintainer,
Please find attached a proposed update for python3-flask-cors in Debian
Bookworm (version 3.0.10-2.2~deb12u1) to fix CVE-2024-1681 (log injection via
CRLF sequences in request path).
This patch is based on the existing 3.0.10-2 source package and **includes the
following**:
- CVE-2024-1681: Request path is now safely escaped using `repr()` before
being logged.
(Upstream commit:
https://github.com/corydolphin/flask-cors/commit/6172c2000dba965fedb8e9a8a916ad56f0fb2630)
- Fixes from bug #1100988 previously submitted as `1100988.debdiff` with
version 3.0.10-2.1~deb12u1 (not yet merged):
- CVE-2024-6839: Improper CORS validation under certain path conditions
- CVE-2024-6844: Case-insensitive path matching issue
- CVE-2024-6866: CORS bypass via trailing path segment
These previous fixes are retained in the patch and applied via
`debian/patches/upstream/fix-flask-cors-CVE-2024-6839_6844_6866.patch`.
The proposed version is:
3.0.10-2.2~deb12u1
The patch was tested with a minimal CVE-2024-1681 PoC script to verify that
unescaped CRLF sequences are no longer logged, and that the previously
addressed vulnerabilities remain fixed.
Please consider including this patch in a future security update. Thank you for
your review and time.
Best regards,
Yang Wang
<[email protected]>
-- System Information:
Debian Release: 12.11
APT prefers stable
APT policy: (500, 'stable')
merged-usr: no
Architecture: amd64 (x86_64)
Kernel: Linux 6.8.0-60-generic (SMP w/8 CPU threads; PREEMPT)
Locale: LANG=C, LC_CTYPE=C (charmap=ANSI_X3.4-1968) (ignored: LC_ALL set to C),
LANGUAGE not set
Shell: /bin/sh linked to /bin/dash
Init: unable to detect
Versions of packages python3-flask-cors depends on:
ii libjs-sphinxdoc 5.3.0-4
ii python3 3.11.2-1+b1
ii python3-flask 2.2.2-3
ii python3-six 1.16.0-4
ii sphinx-rtd-theme-common 1.2.0+dfsg-1
python3-flask-cors recommends no packages.
python3-flask-cors suggests no packages.
-- no debconf information
diff -Nru python-flask-cors-3.0.10/debian/changelog
python-flask-cors-3.0.10/debian/changelog
--- python-flask-cors-3.0.10/debian/changelog 2023-01-22 08:52:05.000000000
+0000
+++ python-flask-cors-3.0.10/debian/changelog 2025-06-19 20:10:15.000000000
+0000
@@ -1,3 +1,26 @@
+python-flask-cors (3.0.10-2.2~deb12u1) bookworm-security; urgency=medium
+
+ * Non-maintainer upload.
+ * Include previous CVE fixes from 3.0.10-2.1~deb12u1 (not yet merged):
+ - CVE-2024-6839, CVE-2024-6844, CVE-2024-6866
+ * Fix CVE-2024-1681: log injection via CRLF in request path
+ - Escape request path in debug log using repr()
+ - Cherry-picked upstream commit: 6172c2000dba965fedb8e9a8a916ad56f0fb2630
+
+ -- Yang Wang <[email protected]> Thu, 19 Jun 2025 16:27:00 -0400
+
+python-flask-cors (3.0.10-2.1~deb12u1) bookworm-security; urgency=medium
+
+ * Non-maintainer upload.
+ * Fix CVE-2024-6839, CVE-2024-6844, CVE-2024-6866:
+ - Improper CORS validation
+ - Path matching logic bugs
+ - CORS bypass with trailing slashes
+ * Backported from upstream fixes.
+ (Closes: #1100988)
+
+ -- Yang Wang <[email protected]> Tue, 17 Jun 2025 17:59:48 +0000
+
python-flask-cors (3.0.10-2) unstable; urgency=medium
* Team upload.
diff -Nru python-flask-cors-3.0.10/debian/patches/series
python-flask-cors-3.0.10/debian/patches/series
--- python-flask-cors-3.0.10/debian/patches/series 2022-11-01
07:15:06.000000000 +0000
+++ python-flask-cors-3.0.10/debian/patches/series 2025-06-19
19:55:10.000000000 +0000
@@ -3,3 +3,5 @@
upstream/Spelling-Fix-misspelled-word-conjuction.patch
upstream/Spelling-Fix-misspelled-word-maching.patch
debian-hacks/docs-Use-local-inventory-for-Python3.patch
+upstream/fix-flask-cors-CVE-2024-6839_6844_6866.patch
+upstream/fix-CVE-2024-1681-log-injection.patch
diff -Nru
python-flask-cors-3.0.10/debian/patches/upstream/fix-CVE-2024-1681-log-injection.patch
python-flask-cors-3.0.10/debian/patches/upstream/fix-CVE-2024-1681-log-injection.patch
---
python-flask-cors-3.0.10/debian/patches/upstream/fix-CVE-2024-1681-log-injection.patch
1970-01-01 00:00:00.000000000 +0000
+++
python-flask-cors-3.0.10/debian/patches/upstream/fix-CVE-2024-1681-log-injection.patch
2025-06-19 20:07:00.000000000 +0000
@@ -0,0 +1,26 @@
+Description: Sanitize request path before logging to avoid CRLF log injection
+ This patch addresses CVE-2024-1681 by escaping special characters in the
+ request path when logging, preventing log injection via newline sequences.
+
+Origin: upstream,
https://github.com/corydolphin/flask-cors/commit/6172c2000dba965fedb8e9a8a916ad56f0fb2630
+Bug: https://github.com/corydolphin/flask-cors/issues/349
+Bug-Debian: https://bugs.debian.org/1069764
+Bug-Security: https://security-tracker.debian.org/tracker/CVE-2024-1681
+Forwarded: yes
+Reviewed-By: Yang Wang <[email protected]>
+Last-Update: 2025-06-19
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+Index: python-flask-cors-3.0.10/flask_cors/extension.py
+===================================================================
+--- python-flask-cors-3.0.10.orig/flask_cors/extension.py
++++ python-flask-cors-3.0.10/flask_cors/extension.py
+@@ -180,7 +180,7 @@ def make_after_request_function(resource
+ normalized_path = unquote(request.path)
+ for res_regex, res_options in resources:
+ if try_match_pattern(normalized_path, res_regex,
caseSensitive=True):
+- LOG.debug("Request to '%s' matches CORS resource '%s'. Using
options: %s",
++ LOG.debug("Request to '%r' matches CORS resource '%s'. Using
options: %s",
+ request.path, get_regexp_pattern(res_regex),
res_options)
+ set_cors_headers(resp, res_options)
+ break
diff -Nru
python-flask-cors-3.0.10/debian/patches/upstream/fix-flask-cors-CVE-2024-6839_6844_6866.patch
python-flask-cors-3.0.10/debian/patches/upstream/fix-flask-cors-CVE-2024-6839_6844_6866.patch
---
python-flask-cors-3.0.10/debian/patches/upstream/fix-flask-cors-CVE-2024-6839_6844_6866.patch
1970-01-01 00:00:00.000000000 +0000
+++
python-flask-cors-3.0.10/debian/patches/upstream/fix-flask-cors-CVE-2024-6839_6844_6866.patch
2025-06-19 19:42:43.000000000 +0000
@@ -0,0 +1,241 @@
+Description: Backport fixes for CVE-2024-6839, CVE-2024-6844, CVE-2024-6866
+ These vulnerabilities affected older versions of Flask-CORS prior to 6.0.0.
+ This patch ports upstream changes to 3.0.10 used in Debian Bookworm.
+Author: Backport by Yang Wang
+Origin: upstream,
https://github.com/corydolphin/flask-cors/compare/5.0.1...6.0.0
+Bug-Debian: https://bugs.debian.org/1100988
+Forwarded: not-needed
+Last-Update: 2025-06-17
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+Index: python-flask-cors-3.0.10/flask_cors/core.py
+===================================================================
+--- python-flask-cors-3.0.10.orig/flask_cors/core.py
++++ python-flask-cors-3.0.10/flask_cors/core.py
+@@ -69,16 +69,17 @@ def parse_resources(resources):
+ # resource of '*', which is not actually a valid regexp.
+ resources = [(re_fix(k), v) for k, v in resources.items()]
+
+- # Sort by regex length to provide consistency of matching and
+- # to provide a proxy for specificity of match. E.G. longer
+- # regular expressions are tried first.
+- def pattern_length(pair):
+- maybe_regex, _ = pair
+- return len(get_regexp_pattern(maybe_regex))
+-
+- return sorted(resources,
+- key=pattern_length,
+- reverse=True)
++ # Sort patterns with static (literal) paths first, then by regex
specificity
++ def sort_key(pair):
++ pattern, _ = pair
++ if isinstance(pattern, RegexObject):
++ return (1, 0, pattern.pattern.count("/"),
-len(pattern.pattern))
++ elif probably_regex(pattern):
++ return (1, 1, pattern.count("/"), -len(pattern))
++ else:
++ return (0, 0, pattern.count("/"), -len(pattern))
++
++ return sorted(resources, key=sort_key)
+
+ elif isinstance(resources, string_types):
+ return [(re_fix(resources), {})]
+@@ -123,10 +124,13 @@ def get_cors_origins(options, request_or
+ 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):
+- LOG.debug("The request's Origin header matches. Sending CORS
headers.", )
++ # If the value of the Origin header is a case-insensitive match
++ # for any of the values in list of origins.Add commentMore actions
++ # 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.",
++ )
+ # Add a single Access-Control-Allow-Origin header, with either
+ # the value of the Origin header or the string "*" as value.
+ # -- W3Spec
+@@ -163,10 +167,7 @@ def get_allow_headers(options, acl_reque
+ 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))
+
+@@ -268,22 +269,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):
+ """
+Index: python-flask-cors-3.0.10/flask_cors/extension.py
+===================================================================
+--- python-flask-cors-3.0.10.orig/flask_cors/extension.py
++++ python-flask-cors-3.0.10/flask_cors/extension.py
+@@ -11,9 +11,9 @@
+ from flask import request
+ from .core import *
+ try:
+- from urllib.parse import unquote_plus
++ from urllib.parse import unquote
+ except ImportError:
+- from urllib import unquote_plus
++ from urllib import unquote
+
+ LOG = logging.getLogger(__name__)
+
+@@ -177,9 +177,9 @@ def make_after_request_function(resource
+ if resp.headers is not None and resp.headers.get(ACL_ORIGIN):
+ LOG.debug('CORS have been already evaluated, skipping')
+ return resp
+- normalized_path = unquote_plus(request.path)
++ normalized_path = unquote(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 '%s' matches CORS resource '%s'. Using
options: %s",
+ request.path, get_regexp_pattern(res_regex),
res_options)
+ set_cors_headers(resp, res_options)
+Index: python-flask-cors-3.0.10/tests/core/helper_tests.py
+===================================================================
+--- python-flask-cors-3.0.10.orig/tests/core/helper_tests.py
++++ python-flask-cors-3.0.10/tests/core/helper_tests.py
+@@ -17,9 +17,12 @@ from flask_cors.core import *
+
+
+ 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.assertEquals(flexible_str('Bar, Foo, Qux'), 'Bar, Foo, Qux')
+@@ -78,7 +81,7 @@ class InternalsTestCase(unittest.TestCas
+
+ self.assertEqual(
+ [r[0] for r in resources],
+- [re.compile(r'/api/v1/.*'), '/foo', re.compile(r'/.*')]
++ ['/foo', re.compile(r'/api/v1/.*'), re.compile(r'/.*')]
+ )
+
+ def test_probably_regex(self):
+Index: python-flask-cors-3.0.10/tests/extension/test_app_extension.py
+===================================================================
+--- python-flask-cors-3.0.10.orig/tests/extension/test_app_extension.py
++++ python-flask-cors-3.0.10/tests/extension/test_app_extension.py
+@@ -378,5 +378,61 @@ class AppExtensionBadRegexp(FlaskCorsTes
+ self.assertEqual(resp.status_code, 200)
+
+
++class AppExtensionPlusInPath(FlaskCorsTestCase):
++ '''
++ Regression test for CVE-2024-6844:
++ Ensures that we correctly differentiate '+' from ' ' in URL paths.
++ '''
++
++ def setUp(self):
++ self.app = Flask(__name__)
++ CORS(self.app, resources={
++ r'/service\+path': {'origins': ['http://foo.com']},
++ r'/service path': {'origins': ['http://bar.com']},
++ })
++
++ @self.app.route('/service+path')
++ def plus_path():
++ return 'plus'
++
++ @self.app.route('/service path')
++ def space_path():
++ return 'space'
++
++ self.client = self.app.test_client()
++
++ def test_plus_path_origin_allowed(self):
++ '''
++ Ensure that CORS matches + literally and allows the correct origin
++ '''
++ response = self.client.get('/service+path', headers={'Origin':
'http://foo.com'})
++ self.assertEqual(response.status_code, 200)
++ self.assertEqual(response.headers.get(ACL_ORIGIN), 'http://foo.com')
++
++ def test_space_path_origin_allowed(self):
++ '''
++ Ensure that CORS treats /service path differently and allows correct
origin
++ '''
++ response = self.client.get('/service%20path', headers={'Origin':
'http://bar.com'})
++ self.assertEqual(response.status_code, 200)
++ self.assertEqual(response.headers.get(ACL_ORIGIN), 'http://bar.com')
++
++ def test_plus_path_rejects_other_origin(self):
++ '''
++ Origin not allowed for + path should be rejected
++ '''
++ response = self.client.get('/service+path', headers={'Origin':
'http://bar.com'})
++ self.assertEqual(response.status_code, 200)
++ self.assertIsNone(response.headers.get(ACL_ORIGIN))
++
++ def test_space_path_rejects_other_origin(self):
++ '''
++ Origin not allowed for space path should be rejected
++ '''
++ response = self.client.get('/service%20path', headers={'Origin':
'http://foo.com'})
++ self.assertEqual(response.status_code, 200)
++ self.assertIsNone(response.headers.get(ACL_ORIGIN))
++
++
+ if __name__ == "__main__":
+ unittest.main()
--- End Message ---