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.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | >= 5.0, < 5.0.7 | 5.0.7 |
DjangoPyPI | >= 4.2, < 4.2.14 | 4.2.14 |
Affected products
30- Django/Djangodescription
- osv-coords29 versionspkg:apk/chainguard/py3.10-djangopkg:apk/chainguard/py3.10-django-binpkg:apk/chainguard/py3.11-djangopkg:apk/chainguard/py3.11-django-binpkg:apk/chainguard/py3.12-djangopkg:apk/chainguard/py3.12-django-binpkg:apk/chainguard/py3.13-djangopkg:apk/chainguard/py3.13-django-binpkg:apk/chainguard/py3-djangopkg:apk/chainguard/py3-supported-djangopkg:apk/wolfi/py3.10-djangopkg:apk/wolfi/py3.10-django-binpkg:apk/wolfi/py3.11-djangopkg:apk/wolfi/py3.11-django-binpkg:apk/wolfi/py3.12-djangopkg:apk/wolfi/py3.12-django-binpkg:apk/wolfi/py3.13-djangopkg:apk/wolfi/py3.13-django-binpkg:apk/wolfi/py3-djangopkg:apk/wolfi/py3-supported-djangopkg:bitnami/djangopkg:pypi/djangopkg:rpm/opensuse/python-Django4&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/python-Django6&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/python-Django&distro=openSUSE%20Leap%2015.5pkg:rpm/opensuse/python-Django&distro=openSUSE%20Leap%2015.6pkg:rpm/opensuse/python-Django&distro=openSUSE%20Tumbleweedpkg:rpm/suse/python-Django&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Package%20Hub%2015%20SP6pkg:rpm/suse/python-Django&distro=SUSE%20Package%20Hub%2015%20SP5
< 5.0.7-r0+ 28 more
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: < 5.0.7-r0
- (no CPE)range: >= 4.2.0, < 4.2.14
- (no CPE)range: >= 5.0, < 5.0.7
- (no CPE)range: < 4.2.14-1.1
- (no CPE)range: < 6.0-1.1
- (no CPE)range: < 2.0.7-150000.1.20.1
- (no CPE)range: < 4.2.11-150600.3.3.1
- (no CPE)range: < 5.0.7-2.1
- (no CPE)range: < 4.2.11-150600.3.3.1
- (no CPE)range: < 2.2.28-bp155.7.12.1
Patches
498cf264c9cb0deec9b933ee88e7a44e4bec0[5.0.x] Fixed CVE-2024-39614 -- Mitigated potential DoS in get_supported_language_variant().
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().
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- github.com/advisories/GHSA-f6f8-9mx6-9mx2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-39614ghsaADVISORY
- docs.djangoproject.com/en/dev/releases/securityghsaWEB
- github.com/django/django/commit/17358fb35fb7217423d4c4877ccb6d1a3a40b1c3ghsaWEB
- github.com/django/django/commit/8e7a44e4bec0f11474699c3111a5e0a45afe7f49ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2024-59.yamlghsaWEB
- groups.google.com/forum/ghsaWEB
- security.netapp.com/advisory/ntap-20240808-0005ghsaWEB
- www.djangoproject.com/weblog/2024/jul/09/security-releasesghsaWEB
- docs.djangoproject.com/en/dev/releases/security/mitre
- www.djangoproject.com/weblog/2024/jul/09/security-releases/mitre
News mentions
0No linked articles in our index yet.