티스토리 수익 글 보기

티스토리 수익 글 보기

[5.0.x] Fixed CVE-2024-39614 — Mitigated potential DoS in get_suppor… · django/django@8e7a44e · GitHub
Skip to content
/ django Public

Commit 8e7a44e

Browse files
sarahboycenessita
authored andcommitted
[5.0.x] Fixed CVE-2024-39614 — Mitigated potential DoS in get_supported_language_variant().
Language codes are now parsed with a maximum length limit of 500 chars. Thanks to MProgrammer for the report.
1 parent 9f4f63e commit 8e7a44e

File tree

5 files changed

+71
5
lines changed

5 files changed

+71
5
lines changed

django/utils/translation/trans_real.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@
3232
CONTEXT_SEPARATOR = "\x04"
3333

3434
# Maximum number of characters that will be parsed from the Accept-Language
35-
# header to prevent possible denial of service or memory exhaustion attacks.
36-
# About 10x longer than the longest value shown on MDN’s Accept-Language page.
37-
ACCEPT_LANGUAGE_HEADER_MAX_LENGTH = 500
35+
# header or cookie to prevent possible denial of service or memory exhaustion
36+
# attacks. About 10x longer than the longest value shown on MDN’s
37+
# Accept-Language page.
38+
LANGUAGE_CODE_MAX_LENGTH = 500
3839

3940
# Format of Accept-Language header values. From RFC 9110 Sections 12.4.2 and
4041
# 12.5.4, and RFC 5646 Section 2.1.
@@ -498,11 +499,25 @@ def get_supported_language_variant(lang_code, strict=False):
498499
If `strict` is False (the default), look for a country-specific variant
499500
when neither the language code nor its generic variant is found.
500501
502+
The language code is truncated to a maximum length to avoid potential
503+
denial of service attacks.
504+
501505
lru_cache should have a maxsize to prevent from memory exhaustion attacks,
502506
as the provided language codes are taken from the HTTP request. See also
503507
<https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>.
504508
"""
505509
if lang_code:
510+
# Truncate the language code to a maximum length to avoid potential
511+
# denial of service attacks.
512+
if len(lang_code) > LANGUAGE_CODE_MAX_LENGTH:
513+
if (
514+
not strict
515+
and (index := lang_code.rfind("-", 0, LANGUAGE_CODE_MAX_LENGTH)) > 0
516+
):
517+
# There is a generic variant under the maximum length accepted length.
518+
lang_code = lang_code[:index]
519+
else:
520+
raise ValueError("'lang_code' exceeds the maximum accepted length")
506521
# If 'zh-hant-tw' is not supported, try special fallback or subsequent
507522
# language codes i.e. 'zh-hant' and 'zh'.
508523
possible_lang_codes = [lang_code]
@@ -626,13 +641,13 @@ def parse_accept_lang_header(lang_string):
626641
functools.lru_cache() to avoid repetitive parsing of common header values.
627642
"""
628643
# If the header value doesn't exceed the maximum allowed length, parse it.
629-
if len(lang_string) <= ACCEPT_LANGUAGE_HEADER_MAX_LENGTH:
644+
if len(lang_string) <= LANGUAGE_CODE_MAX_LENGTH:
630645
return _parse_accept_lang_header(lang_string)
631646

632647
# If there is at least one comma in the value, parse up to the last comma
633648
# before the max length, skipping any truncated parts at the end of the
634649
# header value.
635-
if (index := lang_string.rfind(",", 0, ACCEPT_LANGUAGE_HEADER_MAX_LENGTH)) > 0:
650+
if (index := lang_string.rfind(",", 0, LANGUAGE_CODE_MAX_LENGTH)) > 0:
636651
return _parse_accept_lang_header(lang_string[:index])
637652

638653
# Don't attempt to parse if there is only one language-range value which is

docs/ref/utils.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,11 @@ For a complete discussion on the usage of the following see the
11131113
``lang_code`` is ``'es-ar'`` and ``'es'`` is in :setting:`LANGUAGES` but
11141114
``'es-ar'`` isn't.
11151115

1116+
``lang_code`` has a maximum accepted length of 500 characters. A
1117+
:exc:`ValueError` is raised if ``lang_code`` exceeds this limit and
1118+
``strict`` is ``True``, or if there is no generic variant and ``strict``
1119+
is ``False``.
1120+
11161121
If ``strict`` is ``False`` (the default), a country-specific variant may
11171122
be returned when neither the language code nor its generic variant is found.
11181123
For example, if only ``'es-co'`` is in :setting:`LANGUAGES`, that's
@@ -1121,6 +1126,11 @@ For a complete discussion on the usage of the following see the
11211126

11221127
Raises :exc:`LookupError` if nothing is found.
11231128

1129+
.. versionchanged:: 4.2.14
1130+
1131+
In older versions, ``lang_code`` values over 500 characters were
1132+
processed without raising a :exc:`ValueError`.
1133+
11241134
.. function:: to_locale(language)
11251135

11261136
Turns a language name (en-us) into a locale name (en_US).

docs/releases/4.2.14.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,18 @@ directory-traversal via certain inputs when calling :meth:`save()
3232
<django.core.files.storage.Storage.save()>`.
3333

3434
Built-in ``Storage`` sub-classes were not affected by this vulnerability.
35+
36+
CVE-2024-39614: Potential denial-of-service vulnerability in ``get_supported_language_variant()``
37+
=================================================================================================
38+
39+
:meth:`~django.utils.translation.get_supported_language_variant` was subject to
40+
a potential denial-of-service attack when used with very long strings
41+
containing specific characters.
42+
43+
To mitigate this vulnerability, the language code provided to
44+
:meth:`~django.utils.translation.get_supported_language_variant` is now parsed
45+
up to a maximum length of 500 characters.
46+
47+
When the language code is over 500 characters, a :exc:`ValueError` will now be
48+
raised if ``strict`` is ``True``, or if there is no generic variant and
49+
``strict`` is ``False``.

docs/releases/5.0.7.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ directory-traversal via certain inputs when calling :meth:`save()
3333

3434
Built-in ``Storage`` sub-classes were not affected by this vulnerability.
3535

36+
CVE-2024-39614: Potential denial-of-service vulnerability in ``get_supported_language_variant()``
37+
=================================================================================================
38+
39+
:meth:`~django.utils.translation.get_supported_language_variant` was subject to
40+
a potential denial-of-service attack when used with very long strings
41+
containing specific characters.
42+
43+
To mitigate this vulnerability, the language code provided to
44+
:meth:`~django.utils.translation.get_supported_language_variant` is now parsed
45+
up to a maximum length of 500 characters.
46+
47+
When the language code is over 500 characters, a :exc:`ValueError` will now be
48+
raised if ``strict`` is ``True``, or if there is no generic variant and
49+
``strict`` is ``False``.
50+
3651
Bugfixes
3752
========
3853

tests/i18n/tests.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
translation_file_changed,
5959
watch_for_translation_changes,
6060
)
61+
from django.utils.translation.trans_real import LANGUAGE_CODE_MAX_LENGTH
6162

6263
from .forms import CompanyForm, I18nForm, SelectDateForm
6364
from .models import Company, TestModel
@@ -1672,6 +1673,16 @@ def test_get_supported_language_variant_real(self):
16721673
g("xyz")
16731674
with self.assertRaises(LookupError):
16741675
g("xy-zz")
1676+
msg = "'lang_code' exceeds the maximum accepted length"
1677+
with self.assertRaises(LookupError):
1678+
g("x" * LANGUAGE_CODE_MAX_LENGTH)
1679+
with self.assertRaisesMessage(ValueError, msg):
1680+
g("x" * (LANGUAGE_CODE_MAX_LENGTH + 1))
1681+
# 167 * 3 = 501 which is LANGUAGE_CODE_MAX_LENGTH + 1.
1682+
self.assertEqual(g("en-" * 167), "en")
1683+
with self.assertRaisesMessage(ValueError, msg):
1684+
g("en-" * 167, strict=True)
1685+
self.assertEqual(g("en-" * 30000), "en") # catastrophic test
16751686

16761687
def test_get_supported_language_variant_null(self):
16771688
g = trans_null.get_supported_language_variant

0 commit comments

Comments
 (0)