티스토리 수익 글 보기

티스토리 수익 글 보기

[4.2.x] Fixed CVE-2024-39614 — Mitigated potential DoS in get_suppor… · django/django@17358fb · GitHub
Skip to content

Commit 17358fb

Browse files
sarahboycenessita
authored andcommitted
[4.2.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 2b00edc commit 17358fb

File tree

4 files changed

+56
5
lines changed

4 files changed

+56
5
lines changed

django/utils/translation/trans_real.py

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

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

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

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

637652
# 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
@@ -1155,6 +1155,11 @@ For a complete discussion on the usage of the following see the
11551155
``lang_code`` is ``'es-ar'`` and ``'es'`` is in :setting:`LANGUAGES` but
11561156
``'es-ar'`` isn't.
11571157

1158+
``lang_code`` has a maximum accepted length of 500 characters. A
1159+
:exc:`ValueError` is raised if ``lang_code`` exceeds this limit and
1160+
``strict`` is ``True``, or if there is no generic variant and ``strict``
1161+
is ``False``.
1162+
11581163
If ``strict`` is ``False`` (the default), a country-specific variant may
11591164
be returned when neither the language code nor its generic variant is found.
11601165
For example, if only ``'es-co'`` is in :setting:`LANGUAGES`, that's
@@ -1163,6 +1168,11 @@ For a complete discussion on the usage of the following see the
11631168

11641169
Raises :exc:`LookupError` if nothing is found.
11651170

1171+
.. versionchanged:: 4.2.14
1172+
1173+
In older versions, ``lang_code`` values over 500 characters were
1174+
processed without raising a :exc:`ValueError`.
1175+
11661176
.. function:: to_locale(language)
11671177

11681178
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``.

tests/i18n/tests.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
translation_file_changed,
6666
watch_for_translation_changes,
6767
)
68+
from django.utils.translation.trans_real import LANGUAGE_CODE_MAX_LENGTH
6869

6970
from .forms import CompanyForm, I18nForm, SelectDateForm
7071
from .models import Company, TestModel
@@ -1888,6 +1889,16 @@ def test_get_supported_language_variant_real(self):
18881889
g("xyz")
18891890
with self.assertRaises(LookupError):
18901891
g("xy-zz")
1892+
msg = "'lang_code' exceeds the maximum accepted length"
1893+
with self.assertRaises(LookupError):
1894+
g("x" * LANGUAGE_CODE_MAX_LENGTH)
1895+
with self.assertRaisesMessage(ValueError, msg):
1896+
g("x" * (LANGUAGE_CODE_MAX_LENGTH + 1))
1897+
# 167 * 3 = 501 which is LANGUAGE_CODE_MAX_LENGTH + 1.
1898+
self.assertEqual(g("en-" * 167), "en")
1899+
with self.assertRaisesMessage(ValueError, msg):
1900+
g("en-" * 167, strict=True)
1901+
self.assertEqual(g("en-" * 30000), "en") # catastrophic test
18911902

18921903
def test_get_supported_language_variant_null(self):
18931904
g = trans_null.get_supported_language_variant

0 commit comments

Comments
 (0)