VYPR
High severityNVD Advisory· Published Jul 10, 2024· Updated Nov 4, 2025

CVE-2024-39614

CVE-2024-39614

Description

An issue was discovered in Django 5.0 before 5.0.7 and 4.2 before 4.2.14. get_supported_language_variant() was subject to a potential denial-of-service attack when used with very long strings containing specific characters.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Django's get_supported_language_variant() is vulnerable to denial-of-service via crafted long strings, patched in 5.0.7 and 4.2.14.

The vulnerability resides in the get_supported_language_variant() function, which processes language codes from HTTP Accept-Language headers or cookies. Prior to the fix, there was no maximum length check on the lang_code parameter, allowing an attacker to submit extremely long strings containing specific characters that trigger excessive processing, leading to CPU exhaustion and denial of service [2][3][4].

An unauthenticated remote attacker can exploit this by sending crafted HTTP requests with an oversized Accept-Language header or cookie, causing the server to consume significant CPU resources and potentially become unresponsive [2]. No authentication or special network position is required, making the attack surface broad and easily accessible.

Successful exploitation results in a denial-of-service condition, degrading or completely disrupting the availability of the Django application. The impact is primarily on service uptime and resource availability, as the attack does not lead to data compromise or privilege escalation.

Django has released security patches in versions 5.0.7 and 4.2.14 that address this issue by truncating the language code to a maximum of 500 characters and raising a ValueError for longer inputs [3][4]. Users are strongly advised to upgrade to these versions or apply the relevant patches.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
>= 5.0, < 5.0.75.0.7
DjangoPyPI
>= 4.2, < 4.2.144.2.14

Affected products

30

Patches

4
8e7a44e4bec0

[5.0.x] Fixed CVE-2024-39614 -- Mitigated potential DoS in get_supported_language_variant().

https://github.com/django/djangoSarah BoyceJun 26, 2024via ghsa
5 files changed · +71 5
  • django/utils/translation/trans_real.py+20 5 modified
    @@ -32,9 +32,10 @@
     CONTEXT_SEPARATOR = "\x04"
     
     # Maximum number of characters that will be parsed from the Accept-Language
    -# header to prevent possible denial of service or memory exhaustion attacks.
    -# About 10x longer than the longest value shown on MDN’s Accept-Language page.
    -ACCEPT_LANGUAGE_HEADER_MAX_LENGTH = 500
    +# header or cookie to prevent possible denial of service or memory exhaustion
    +# attacks. About 10x longer than the longest value shown on MDN’s
    +# Accept-Language page.
    +LANGUAGE_CODE_MAX_LENGTH = 500
     
     # Format of Accept-Language header values. From RFC 9110 Sections 12.4.2 and
     # 12.5.4, and RFC 5646 Section 2.1.
    @@ -498,11 +499,25 @@ def get_supported_language_variant(lang_code, strict=False):
         If `strict` is False (the default), look for a country-specific variant
         when neither the language code nor its generic variant is found.
     
    +    The language code is truncated to a maximum length to avoid potential
    +    denial of service attacks.
    +
         lru_cache should have a maxsize to prevent from memory exhaustion attacks,
         as the provided language codes are taken from the HTTP request. See also
         <https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>.
         """
         if lang_code:
    +        # Truncate the language code to a maximum length to avoid potential
    +        # denial of service attacks.
    +        if len(lang_code) > LANGUAGE_CODE_MAX_LENGTH:
    +            if (
    +                not strict
    +                and (index := lang_code.rfind("-", 0, LANGUAGE_CODE_MAX_LENGTH)) > 0
    +            ):
    +                # There is a generic variant under the maximum length accepted length.
    +                lang_code = lang_code[:index]
    +            else:
    +                raise ValueError("'lang_code' exceeds the maximum accepted length")
             # If 'zh-hant-tw' is not supported, try special fallback or subsequent
             # language codes i.e. 'zh-hant' and 'zh'.
             possible_lang_codes = [lang_code]
    @@ -626,13 +641,13 @@ def parse_accept_lang_header(lang_string):
         functools.lru_cache() to avoid repetitive parsing of common header values.
         """
         # If the header value doesn't exceed the maximum allowed length, parse it.
    -    if len(lang_string) <= ACCEPT_LANGUAGE_HEADER_MAX_LENGTH:
    +    if len(lang_string) <= LANGUAGE_CODE_MAX_LENGTH:
             return _parse_accept_lang_header(lang_string)
     
         # If there is at least one comma in the value, parse up to the last comma
         # before the max length, skipping any truncated parts at the end of the
         # header value.
    -    if (index := lang_string.rfind(",", 0, ACCEPT_LANGUAGE_HEADER_MAX_LENGTH)) > 0:
    +    if (index := lang_string.rfind(",", 0, LANGUAGE_CODE_MAX_LENGTH)) > 0:
             return _parse_accept_lang_header(lang_string[:index])
     
         # Don't attempt to parse if there is only one language-range value which is
    
  • docs/ref/utils.txt+10 0 modified
    @@ -1113,6 +1113,11 @@ For a complete discussion on the usage of the following see the
         ``lang_code`` is ``'es-ar'`` and ``'es'`` is in :setting:`LANGUAGES` but
         ``'es-ar'`` isn't.
     
    +    ``lang_code`` has a maximum accepted length of 500 characters. A
    +    :exc:`ValueError` is raised if ``lang_code`` exceeds this limit and
    +    ``strict`` is ``True``, or if there is no generic variant and ``strict``
    +    is ``False``.
    +
         If ``strict`` is ``False`` (the default), a country-specific variant may
         be returned when neither the language code nor its generic variant is found.
         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
     
         Raises :exc:`LookupError` if nothing is found.
     
    +    .. versionchanged:: 4.2.14
    +
    +        In older versions, ``lang_code`` values over 500 characters were
    +        processed without raising a :exc:`ValueError`.
    +
     .. function:: to_locale(language)
     
         Turns a language name (en-us) into a locale name (en_US).
    
  • docs/releases/4.2.14.txt+15 0 modified
    @@ -32,3 +32,18 @@ directory-traversal via certain inputs when calling :meth:`save()
     <django.core.files.storage.Storage.save()>`.
     
     Built-in ``Storage`` sub-classes were not affected by this vulnerability.
    +
    +CVE-2024-39614: Potential denial-of-service vulnerability in ``get_supported_language_variant()``
    +=================================================================================================
    +
    +:meth:`~django.utils.translation.get_supported_language_variant` was subject to
    +a potential denial-of-service attack when used with very long strings
    +containing specific characters.
    +
    +To mitigate this vulnerability, the language code provided to
    +:meth:`~django.utils.translation.get_supported_language_variant` is now parsed
    +up to a maximum length of 500 characters.
    +
    +When the language code is over 500 characters, a :exc:`ValueError` will now be
    +raised if ``strict`` is ``True``, or if there is no generic variant and
    +``strict`` is ``False``.
    
  • docs/releases/5.0.7.txt+15 0 modified
    @@ -33,6 +33,21 @@ directory-traversal via certain inputs when calling :meth:`save()
     
     Built-in ``Storage`` sub-classes were not affected by this vulnerability.
     
    +CVE-2024-39614: Potential denial-of-service vulnerability in ``get_supported_language_variant()``
    +=================================================================================================
    +
    +:meth:`~django.utils.translation.get_supported_language_variant` was subject to
    +a potential denial-of-service attack when used with very long strings
    +containing specific characters.
    +
    +To mitigate this vulnerability, the language code provided to
    +:meth:`~django.utils.translation.get_supported_language_variant` is now parsed
    +up to a maximum length of 500 characters.
    +
    +When the language code is over 500 characters, a :exc:`ValueError` will now be
    +raised if ``strict`` is ``True``, or if there is no generic variant and
    +``strict`` is ``False``.
    +
     Bugfixes
     ========
     
    
  • tests/i18n/tests.py+11 0 modified
    @@ -58,6 +58,7 @@
         translation_file_changed,
         watch_for_translation_changes,
     )
    +from django.utils.translation.trans_real import LANGUAGE_CODE_MAX_LENGTH
     
     from .forms import CompanyForm, I18nForm, SelectDateForm
     from .models import Company, TestModel
    @@ -1672,6 +1673,16 @@ def test_get_supported_language_variant_real(self):
                 g("xyz")
             with self.assertRaises(LookupError):
                 g("xy-zz")
    +        msg = "'lang_code' exceeds the maximum accepted length"
    +        with self.assertRaises(LookupError):
    +            g("x" * LANGUAGE_CODE_MAX_LENGTH)
    +        with self.assertRaisesMessage(ValueError, msg):
    +            g("x" * (LANGUAGE_CODE_MAX_LENGTH + 1))
    +        # 167 * 3 = 501 which is LANGUAGE_CODE_MAX_LENGTH + 1.
    +        self.assertEqual(g("en-" * 167), "en")
    +        with self.assertRaisesMessage(ValueError, msg):
    +            g("en-" * 167, strict=True)
    +        self.assertEqual(g("en-" * 30000), "en")  # catastrophic test
     
         def test_get_supported_language_variant_null(self):
             g = trans_null.get_supported_language_variant
    
17358fb35fb7

[4.2.x] Fixed CVE-2024-39614 -- Mitigated potential DoS in get_supported_language_variant().

https://github.com/django/djangoSarah BoyceJun 26, 2024via ghsa
4 files changed · +56 5
  • django/utils/translation/trans_real.py+20 5 modified
    @@ -31,9 +31,10 @@
     CONTEXT_SEPARATOR = "\x04"
     
     # Maximum number of characters that will be parsed from the Accept-Language
    -# header to prevent possible denial of service or memory exhaustion attacks.
    -# About 10x longer than the longest value shown on MDN’s Accept-Language page.
    -ACCEPT_LANGUAGE_HEADER_MAX_LENGTH = 500
    +# header or cookie to prevent possible denial of service or memory exhaustion
    +# attacks. About 10x longer than the longest value shown on MDN’s
    +# Accept-Language page.
    +LANGUAGE_CODE_MAX_LENGTH = 500
     
     # Format of Accept-Language header values. From RFC 9110 Sections 12.4.2 and
     # 12.5.4, and RFC 5646 Section 2.1.
    @@ -497,11 +498,25 @@ def get_supported_language_variant(lang_code, strict=False):
         If `strict` is False (the default), look for a country-specific variant
         when neither the language code nor its generic variant is found.
     
    +    The language code is truncated to a maximum length to avoid potential
    +    denial of service attacks.
    +
         lru_cache should have a maxsize to prevent from memory exhaustion attacks,
         as the provided language codes are taken from the HTTP request. See also
         <https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>.
         """
         if lang_code:
    +        # Truncate the language code to a maximum length to avoid potential
    +        # denial of service attacks.
    +        if len(lang_code) > LANGUAGE_CODE_MAX_LENGTH:
    +            if (
    +                not strict
    +                and (index := lang_code.rfind("-", 0, LANGUAGE_CODE_MAX_LENGTH)) > 0
    +            ):
    +                # There is a generic variant under the maximum length accepted length.
    +                lang_code = lang_code[:index]
    +            else:
    +                raise ValueError("'lang_code' exceeds the maximum accepted length")
             # If 'zh-hant-tw' is not supported, try special fallback or subsequent
             # language codes i.e. 'zh-hant' and 'zh'.
             possible_lang_codes = [lang_code]
    @@ -625,13 +640,13 @@ def parse_accept_lang_header(lang_string):
         functools.lru_cache() to avoid repetitive parsing of common header values.
         """
         # If the header value doesn't exceed the maximum allowed length, parse it.
    -    if len(lang_string) <= ACCEPT_LANGUAGE_HEADER_MAX_LENGTH:
    +    if len(lang_string) <= LANGUAGE_CODE_MAX_LENGTH:
             return _parse_accept_lang_header(lang_string)
     
         # If there is at least one comma in the value, parse up to the last comma
         # before the max length, skipping any truncated parts at the end of the
         # header value.
    -    if (index := lang_string.rfind(",", 0, ACCEPT_LANGUAGE_HEADER_MAX_LENGTH)) > 0:
    +    if (index := lang_string.rfind(",", 0, LANGUAGE_CODE_MAX_LENGTH)) > 0:
             return _parse_accept_lang_header(lang_string[:index])
     
         # Don't attempt to parse if there is only one language-range value which is
    
  • docs/ref/utils.txt+10 0 modified
    @@ -1155,6 +1155,11 @@ For a complete discussion on the usage of the following see the
         ``lang_code`` is ``'es-ar'`` and ``'es'`` is in :setting:`LANGUAGES` but
         ``'es-ar'`` isn't.
     
    +    ``lang_code`` has a maximum accepted length of 500 characters. A
    +    :exc:`ValueError` is raised if ``lang_code`` exceeds this limit and
    +    ``strict`` is ``True``, or if there is no generic variant and ``strict``
    +    is ``False``.
    +
         If ``strict`` is ``False`` (the default), a country-specific variant may
         be returned when neither the language code nor its generic variant is found.
         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
     
         Raises :exc:`LookupError` if nothing is found.
     
    +    .. versionchanged:: 4.2.14
    +
    +        In older versions, ``lang_code`` values over 500 characters were
    +        processed without raising a :exc:`ValueError`.
    +
     .. function:: to_locale(language)
     
         Turns a language name (en-us) into a locale name (en_US).
    
  • docs/releases/4.2.14.txt+15 0 modified
    @@ -32,3 +32,18 @@ directory-traversal via certain inputs when calling :meth:`save()
     <django.core.files.storage.Storage.save()>`.
     
     Built-in ``Storage`` sub-classes were not affected by this vulnerability.
    +
    +CVE-2024-39614: Potential denial-of-service vulnerability in ``get_supported_language_variant()``
    +=================================================================================================
    +
    +:meth:`~django.utils.translation.get_supported_language_variant` was subject to
    +a potential denial-of-service attack when used with very long strings
    +containing specific characters.
    +
    +To mitigate this vulnerability, the language code provided to
    +:meth:`~django.utils.translation.get_supported_language_variant` is now parsed
    +up to a maximum length of 500 characters.
    +
    +When the language code is over 500 characters, a :exc:`ValueError` will now be
    +raised if ``strict`` is ``True``, or if there is no generic variant and
    +``strict`` is ``False``.
    
  • tests/i18n/tests.py+11 0 modified
    @@ -65,6 +65,7 @@
         translation_file_changed,
         watch_for_translation_changes,
     )
    +from django.utils.translation.trans_real import LANGUAGE_CODE_MAX_LENGTH
     
     from .forms import CompanyForm, I18nForm, SelectDateForm
     from .models import Company, TestModel
    @@ -1888,6 +1889,16 @@ def test_get_supported_language_variant_real(self):
                 g("xyz")
             with self.assertRaises(LookupError):
                 g("xy-zz")
    +        msg = "'lang_code' exceeds the maximum accepted length"
    +        with self.assertRaises(LookupError):
    +            g("x" * LANGUAGE_CODE_MAX_LENGTH)
    +        with self.assertRaisesMessage(ValueError, msg):
    +            g("x" * (LANGUAGE_CODE_MAX_LENGTH + 1))
    +        # 167 * 3 = 501 which is LANGUAGE_CODE_MAX_LENGTH + 1.
    +        self.assertEqual(g("en-" * 167), "en")
    +        with self.assertRaisesMessage(ValueError, msg):
    +            g("en-" * 167, strict=True)
    +        self.assertEqual(g("en-" * 30000), "en")  # catastrophic test
     
         def test_get_supported_language_variant_null(self):
             g = trans_null.get_supported_language_variant
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

11

News mentions

0

No linked articles in our index yet.