CVE-2015-5144
Description
Django before 1.4.21, 1.5.x through 1.6.x, 1.7.x before 1.7.9, and 1.8.x before 1.8.3 uses an incorrect regular expression, which allows remote attackers to inject arbitrary headers and conduct HTTP response splitting attacks via a newline character in an (1) email message to the EmailValidator, a (2) URL to the URLValidator, or unspecified vectors to the (3) validate_ipv4_address or (4) validate_slug validator.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | < 1.4.21 | 1.4.21 |
DjangoPyPI | >= 1.5, < 1.7.9 | 1.7.9 |
DjangoPyPI | >= 1.8a1, < 1.8.3 | 1.8.3 |
Affected products
58cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*+ 50 more
- cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*range: <=1.4.20
- cpe:2.3:a:djangoproject:django:1.5:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.10:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.11:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.12:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.4:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.5:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.6:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.7:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.8:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5.9:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5:alpha:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5:beta:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6:-:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.10:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.4:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.5:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.6:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.7:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.8:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.9:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6:beta1:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6:beta2:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6:beta3:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6:beta4:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.4:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.5:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.6:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.7:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.8:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.9:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7:beta1:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7:beta2:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7:beta3:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7:beta4:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7:rc1:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7:rc2:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7:rc3:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.8.0:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.8.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.8.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.8:beta1:*:*:*:*:*:*
cpe:2.3:o:canonical:ubuntu_linux:12.04:*:*:*:lts:*:*:*+ 3 more
- cpe:2.3:o:canonical:ubuntu_linux:12.04:*:*:*:lts:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:14.04:*:*:*:lts:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:15.04:*:*:*:*:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:15.10:*:*:*:*:*:*:*
cpe:2.3:o:debian:debian_linux:7.0:*:*:*:*:*:*:*+ 1 more
- cpe:2.3:o:debian:debian_linux:7.0:*:*:*:*:*:*:*
- cpe:2.3:o:debian:debian_linux:8.0:*:*:*:*:*:*:*
- cpe:2.3:o:oracle:solaris:11.3:*:*:*:*:*:*:*
Patches
48f9a4d3a2bc4[1.8.x] Fixed catastrophic backtracking in URLValidator.
5 files changed · +14 −1
django/core/validators.py+1 −1 modified@@ -73,7 +73,7 @@ class URLValidator(RegexValidator): # Host patterns hostname_re = r'[a-z' + ul + r'0-9](?:[a-z' + ul + r'0-9-]*[a-z' + ul + r'0-9])?' - domain_re = r'(?:\.[a-z' + ul + r'0-9]+(?:[a-z' + ul + r'0-9-]*[a-z' + ul + r'0-9]+)*)*' + domain_re = r'(?:\.(?!-)[a-z' + ul + r'0-9-]*(?<!-))*' tld_re = r'\.(?:[a-z' + ul + r']{2,}|xn--[a-z0-9]+)\.?' host_re = '(' + hostname_re + domain_re + tld_re + '|localhost)'
docs/releases/1.8.3.txt+7 −0 modified@@ -60,6 +60,13 @@ The undocumented, internally unused ``validate_integer()`` function is now stricter as it validates using a regular expression instead of simply casting the value using ``int()`` and checking if an exception was raised. +Denial-of-service possibility in URL validation +=============================================== + +:class:`~django.core.validators.URLValidator` included a regular expression +that was extremely slow to evaluate against certain invalid inputs. This regular +expression has been simplified and optimized. + Bugfixes ========
tests/validators/invalid_urls.txt+2 −0 modified@@ -35,6 +35,8 @@ http://foo.bar/foo(bar)baz quux http://-error-.invalid/ http://-a.b.co http://a.b-.co +http://a.-b.co +http://a.b-.c.co http:/ http:// http://
tests/validators/tests.py+3 −0 modified@@ -172,6 +172,9 @@ # Trailing newlines not accepted (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError), (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError), + # Trailing junk does not take forever to reject + (URLValidator(), 'http://www.asdasdasdasdsadfm.com.br ', ValidationError), + (URLValidator(), 'http://www.asdasdasdasdsadfm.com.br z', ValidationError), (BaseValidator(True), True, None), (BaseValidator(True), False, ValidationError),
tests/validators/valid_urls.txt+1 −0 modified@@ -7,6 +7,7 @@ http://www.example.com/ http://www.example.com:8000/test http://valid-with-hyphens.com/ http://subdomain.example.com/ +http://a.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa http://200.8.9.10/ http://200.8.9.10:8000/test http://su--b.valid-----hyphens.com/
1ba1cdce7d58[1.4.x] Prevented newlines from being accepted in some validators.
3 files changed · +56 −12
django/core/validators.py+15 −11 modified@@ -50,7 +50,7 @@ class URLValidator(RegexValidator): r'localhost|' #localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip r'(?::\d+)?' # optional port - r'(?:/?|[/?]\S+)$', re.IGNORECASE) + r'(?:/?|[/?]\S+)\Z', re.IGNORECASE) def __init__(self, verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT): @@ -133,11 +133,16 @@ def __call__(self, value): raise broken_error +integer_validator = RegexValidator( + re.compile('^-?\d+\Z'), + message=_('Enter a valid integer.'), + code='invalid', +) + + def validate_integer(value): - try: - int(value) - except (ValueError, TypeError): - raise ValidationError('') + return integer_validator(value) + class EmailValidator(RegexValidator): @@ -160,14 +165,14 @@ def __call__(self, value): r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom # quoted-string, see also http://tools.ietf.org/html/rfc2822#section-3.2.5 r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' - r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$)' # domain - r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3) + r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?\Z)' # domain + r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3) validate_email = EmailValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid') -slug_re = re.compile(r'^[-\w]+$') +slug_re = re.compile(r'^[-\w]+\Z') validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid') -ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') +ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z') validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid') def validate_ipv6_address(value): @@ -205,7 +210,7 @@ def ip_address_validators(protocol, unpack_ipv4): raise ValueError("The protocol '%s' is unknown. Supported: %s" % (protocol, ip_address_validator_map.keys())) -comma_separated_int_list_re = re.compile('^[\d,]+$') +comma_separated_int_list_re = re.compile('^[\d,]+\Z') validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid') @@ -249,4 +254,3 @@ class MaxLengthValidator(BaseValidator): clean = lambda self, x: len(x) message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).') code = 'max_length' -
docs/releases/1.4.21.txt+26 −0 modified@@ -26,3 +26,29 @@ As each built-in session backend was fixed separately (rather than a fix in the core sessions framework), maintainers of third-party session backends should check whether the same vulnerability is present in their backend and correct it if so. + +Header injection possibility since validators accept newlines in input +====================================================================== + +Some of Django's built-in validators +(``django.core.validators.EmailValidator``, most seriously) didn't +prohibit newline characters (due to the usage of ``$`` instead of ``\Z`` in the +regular expressions). If you use values with newlines in HTTP response or email +headers, you can suffer from header injection attacks. Django itself isn't +vulnerable because :class:`~django.http.HttpResponse` and the mail sending +utilities in :mod:`django.core.mail` prohibit newlines in HTTP and SMTP +headers, respectively. While the validators have been fixed in Django, if +you're creating HTTP responses or email messages in other ways, it's a good +idea to ensure that those methods prohibit newlines as well. You might also +want to validate that any existing data in your application doesn't contain +unexpected newlines. + +:func:`~django.core.validators.validate_ipv4_address`, +:func:`~django.core.validators.validate_slug`, and +:class:`~django.core.validators.URLValidator` and their usage in the +corresponding form fields ``GenericIPAddresseField``, ``IPAddressField``, +``SlugField``, and ``URLField`` are also affected. + +The undocumented, internally unused ``validate_integer()`` function is now +stricter as it validates using a regular expression instead of simply casting +the value using ``int()`` and checking if an exception was raised.
tests/modeltests/validators/tests.py+15 −1 modified@@ -11,14 +11,17 @@ NOW = datetime.now() TEST_DATA = ( + # (validator, value, expected), # (validator, value, expected), (validate_integer, '42', None), (validate_integer, '-42', None), (validate_integer, -42, None), - (validate_integer, -42.5, None), + (validate_integer, -42.5, ValidationError), (validate_integer, None, ValidationError), (validate_integer, 'a', ValidationError), + (validate_integer, '\n42', ValidationError), + (validate_integer, '42\n', ValidationError), (validate_email, 'email@here.com', None), (validate_email, 'weirder-email@here.and.there.com', None), @@ -33,6 +36,11 @@ # Quoted-string format (CR not allowed) (validate_email, '"\\\011"@here.com', None), (validate_email, '"\\\012"@here.com', ValidationError), + # Trailing newlines in username or domain not allowed + (validate_email, 'a@b.com\n', ValidationError), + (validate_email, 'a\n@b.com', ValidationError), + (validate_email, '"test@test"\n@example.com', ValidationError), + (validate_email, 'a@[127.0.0.1]\n', ValidationError), (validate_slug, 'slug-ok', None), (validate_slug, 'longer-slug-still-ok', None), @@ -45,6 +53,7 @@ (validate_slug, 'some@mail.com', ValidationError), (validate_slug, '你好', ValidationError), (validate_slug, '\n', ValidationError), + (validate_slug, 'trailing-newline\n', ValidationError), (validate_ipv4_address, '1.1.1.1', None), (validate_ipv4_address, '255.0.0.0', None), @@ -54,6 +63,7 @@ (validate_ipv4_address, '25.1.1.', ValidationError), (validate_ipv4_address, '25,1,1,1', ValidationError), (validate_ipv4_address, '25.1 .1.1', ValidationError), + (validate_ipv4_address, '1.1.1.1\n', ValidationError), # validate_ipv6_address uses django.utils.ipv6, which # is tested in much greater detail in it's own testcase @@ -87,6 +97,7 @@ (validate_comma_separated_integer_list, '', ValidationError), (validate_comma_separated_integer_list, 'a,b,c', ValidationError), (validate_comma_separated_integer_list, '1, 2, 3', ValidationError), + (validate_comma_separated_integer_list, '1,2,3\n', ValidationError), (MaxValueValidator(10), 10, None), (MaxValueValidator(10), -10, None), @@ -138,6 +149,9 @@ (URLValidator(), 'http://-invalid.com', ValidationError), (URLValidator(), 'http://inv-.alid-.com', ValidationError), (URLValidator(), 'http://inv-.-alid.com', ValidationError), + # Trailing newlines not accepted + (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError), + (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError), (BaseValidator(True), True, None), (BaseValidator(True), False, ValidationError),
574dd5e0b0fb[1.8.x] Prevented newlines from being accepted in some validators.
5 files changed · +111 −13
django/core/validators.py+15 −12 modified@@ -83,7 +83,7 @@ class URLValidator(RegexValidator): r'(?:' + ipv4_re + '|' + ipv6_re + '|' + host_re + ')' r'(?::\d{2,5})?' # port r'(?:[/?#][^\s]*)?' # resource path - r'$', re.IGNORECASE) + r'\Z', re.IGNORECASE) message = _('Enter a valid URL.') schemes = ['http', 'https', 'ftp', 'ftps'] @@ -125,30 +125,33 @@ def __call__(self, value): raise ValidationError(self.message, code=self.code) url = value +integer_validator = RegexValidator( + re.compile('^-?\d+\Z'), + message=_('Enter a valid integer.'), + code='invalid', +) + def validate_integer(value): - try: - int(value) - except (ValueError, TypeError): - raise ValidationError(_('Enter a valid integer.'), code='invalid') + return integer_validator(value) @deconstructible class EmailValidator(object): message = _('Enter a valid email address.') code = 'invalid' user_regex = re.compile( - r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$" # dot-atom - r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)', # quoted-string + r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" # dot-atom + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)', # quoted-string re.IGNORECASE) domain_regex = re.compile( # max length of the domain is 249: 254 (max email length) minus one # period, two characters for the TLD, @ sign, & one character before @. - r'(?:[A-Z0-9](?:[A-Z0-9-]{0,247}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))$', + r'(?:[A-Z0-9](?:[A-Z0-9-]{0,247}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z', re.IGNORECASE) literal_regex = re.compile( # literal form, ipv4 or ipv6 address (SMTP 4.1.3) - r'\[([A-f0-9:\.]+)\]$', + r'\[([A-f0-9:\.]+)\]\Z', re.IGNORECASE) domain_whitelist = ['localhost'] @@ -206,14 +209,14 @@ def __eq__(self, other): validate_email = EmailValidator() -slug_re = re.compile(r'^[-a-zA-Z0-9_]+$') +slug_re = re.compile(r'^[-a-zA-Z0-9_]+\Z') validate_slug = RegexValidator( slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid' ) -ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') +ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z') validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid') @@ -254,7 +257,7 @@ def ip_address_validators(protocol, unpack_ipv4): raise ValueError("The protocol '%s' is unknown. Supported: %s" % (protocol, list(ip_address_validator_map))) -comma_separated_int_list_re = re.compile('^[\d,]+$') +comma_separated_int_list_re = re.compile('^[\d,]+\Z') validate_comma_separated_integer_list = RegexValidator( comma_separated_int_list_re, _('Enter only digits separated by commas.'),
docs/releases/1.4.21.txt+26 −0 modified@@ -26,3 +26,29 @@ As each built-in session backend was fixed separately (rather than a fix in the core sessions framework), maintainers of third-party session backends should check whether the same vulnerability is present in their backend and correct it if so. + +Header injection possibility since validators accept newlines in input +====================================================================== + +Some of Django's built-in validators +(:class:`~django.core.validators.EmailValidator`, most seriously) didn't +prohibit newline characters (due to the usage of ``$`` instead of ``\Z`` in the +regular expressions). If you use values with newlines in HTTP response or email +headers, you can suffer from header injection attacks. Django itself isn't +vulnerable because :class:`~django.http.HttpResponse` and the mail sending +utilities in :mod:`django.core.mail` prohibit newlines in HTTP and SMTP +headers, respectively. While the validators have been fixed in Django, if +you're creating HTTP responses or email messages in other ways, it's a good +idea to ensure that those methods prohibit newlines as well. You might also +want to validate that any existing data in your application doesn't contain +unexpected newlines. + +:func:`~django.core.validators.validate_ipv4_address`, +:func:`~django.core.validators.validate_slug`, and +:class:`~django.core.validators.URLValidator` and their usage in the +corresponding form fields ``GenericIPAddresseField``, ``IPAddressField``, +``SlugField``, and ``URLField`` are also affected. + +The undocumented, internally unused ``validate_integer()`` function is now +stricter as it validates using a regular expression instead of simply casting +the value using ``int()`` and checking if an exception was raised.
docs/releases/1.7.9.txt+28 −0 modified@@ -27,6 +27,34 @@ core sessions framework), maintainers of third-party session backends should check whether the same vulnerability is present in their backend and correct it if so. +Header injection possibility since validators accept newlines in input +====================================================================== + +Some of Django's built-in validators +(:class:`~django.core.validators.EmailValidator`, most seriously) didn't +prohibit newline characters (due to the usage of ``$`` instead of ``\Z`` in the +regular expressions). If you use values with newlines in HTTP response or email +headers, you can suffer from header injection attacks. Django itself isn't +vulnerable because :class:`~django.http.HttpResponse` and the mail sending +utilities in :mod:`django.core.mail` prohibit newlines in HTTP and SMTP +headers, respectively. While the validators have been fixed in Django, if +you're creating HTTP responses or email messages in other ways, it's a good +idea to ensure that those methods prohibit newlines as well. You might also +want to validate that any existing data in your application doesn't contain +unexpected newlines. + +:func:`~django.core.validators.validate_ipv4_address`, +:func:`~django.core.validators.validate_slug`, and +:class:`~django.core.validators.URLValidator` are also affected, however, as +of Django 1.6 the ``GenericIPAddresseField``, ``IPAddressField``, ``SlugField``, +and ``URLField`` form fields which use these validators all strip the input, so +the possibility of newlines entering your data only exists if you are using +these validators outside of the form fields. + +The undocumented, internally unused ``validate_integer()`` function is now +stricter as it validates using a regular expression instead of simply casting +the value using ``int()`` and checking if an exception was raised. + Bugfixes ========
docs/releases/1.8.3.txt+28 −0 modified@@ -32,6 +32,34 @@ core sessions framework), maintainers of third-party session backends should check whether the same vulnerability is present in their backend and correct it if so. +Header injection possibility since validators accept newlines in input +====================================================================== + +Some of Django's built-in validators +(:class:`~django.core.validators.EmailValidator`, most seriously) didn't +prohibit newline characters (due to the usage of ``$`` instead of ``\Z`` in the +regular expressions). If you use values with newlines in HTTP response or email +headers, you can suffer from header injection attacks. Django itself isn't +vulnerable because :class:`~django.http.HttpResponse` and the mail sending +utilities in :mod:`django.core.mail` prohibit newlines in HTTP and SMTP +headers, respectively. While the validators have been fixed in Django, if +you're creating HTTP responses or email messages in other ways, it's a good +idea to ensure that those methods prohibit newlines as well. You might also +want to validate that any existing data in your application doesn't contain +unexpected newlines. + +:func:`~django.core.validators.validate_ipv4_address`, +:func:`~django.core.validators.validate_slug`, and +:class:`~django.core.validators.URLValidator` are also affected, however, as +of Django 1.6 the ``GenericIPAddresseField``, ``IPAddressField``, ``SlugField``, +and ``URLField`` form fields which use these validators all strip the input, so +the possibility of newlines entering your data only exists if you are using +these validators outside of the form fields. + +The undocumented, internally unused ``validate_integer()`` function is now +stricter as it validates using a regular expression instead of simply casting +the value using ``int()`` and checking if an exception was raised. + Bugfixes ========
tests/validators/tests.py+14 −1 modified@@ -28,10 +28,12 @@ (validate_integer, '42', None), (validate_integer, '-42', None), (validate_integer, -42, None), - (validate_integer, -42.5, None), + (validate_integer, -42.5, ValidationError), (validate_integer, None, ValidationError), (validate_integer, 'a', ValidationError), + (validate_integer, '\n42', ValidationError), + (validate_integer, '42\n', ValidationError), (validate_email, 'email@here.com', None), (validate_email, 'weirder-email@here.and.there.com', None), @@ -72,6 +74,11 @@ # Max length of domain name in email is 249 (see validator for calculation) (validate_email, 'a@%s.us' % ('a' * 249), None), (validate_email, 'a@%s.us' % ('a' * 250), ValidationError), + # Trailing newlines in username or domain not allowed + (validate_email, 'a@b.com\n', ValidationError), + (validate_email, 'a\n@b.com', ValidationError), + (validate_email, '"test@test"\n@example.com', ValidationError), + (validate_email, 'a@[127.0.0.1]\n', ValidationError), (validate_slug, 'slug-ok', None), (validate_slug, 'longer-slug-still-ok', None), @@ -84,6 +91,7 @@ (validate_slug, 'some@mail.com', ValidationError), (validate_slug, '你好', ValidationError), (validate_slug, '\n', ValidationError), + (validate_slug, 'trailing-newline\n', ValidationError), (validate_ipv4_address, '1.1.1.1', None), (validate_ipv4_address, '255.0.0.0', None), @@ -93,6 +101,7 @@ (validate_ipv4_address, '25.1.1.', ValidationError), (validate_ipv4_address, '25,1,1,1', ValidationError), (validate_ipv4_address, '25.1 .1.1', ValidationError), + (validate_ipv4_address, '1.1.1.1\n', ValidationError), # validate_ipv6_address uses django.utils.ipv6, which # is tested in much greater detail in its own testcase @@ -126,6 +135,7 @@ (validate_comma_separated_integer_list, '', ValidationError), (validate_comma_separated_integer_list, 'a,b,c', ValidationError), (validate_comma_separated_integer_list, '1, 2, 3', ValidationError), + (validate_comma_separated_integer_list, '1,2,3\n', ValidationError), (MaxValueValidator(10), 10, None), (MaxValueValidator(10), -10, None), @@ -159,6 +169,9 @@ (URLValidator(EXTENDED_SCHEMES), 'git://example.com/', None), (URLValidator(EXTENDED_SCHEMES), 'git://-invalid.com', ValidationError), + # Trailing newlines not accepted + (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError), + (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError), (BaseValidator(True), True, None), (BaseValidator(True), False, ValidationError),
ae49b4d99465[1.7.x] Prevented newlines from being accepted in some validators.
4 files changed · +83 −13
django/core/validators.py+15 −12 modified@@ -73,7 +73,7 @@ class URLValidator(RegexValidator): r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 r'(?::\d+)?' # optional port - r'(?:/?|[/?]\S+)$', re.IGNORECASE) + r'(?:/?|[/?]\S+)\Z', re.IGNORECASE) message = _('Enter a valid URL.') schemes = ['http', 'https', 'ftp', 'ftps'] @@ -107,28 +107,31 @@ def __call__(self, value): else: url = value +integer_validator = RegexValidator( + re.compile('^-?\d+\Z'), + message=_('Enter a valid integer.'), + code='invalid', +) + def validate_integer(value): - try: - int(value) - except (ValueError, TypeError): - raise ValidationError(_('Enter a valid integer.'), code='invalid') + return integer_validator(value) @deconstructible class EmailValidator(object): message = _('Enter a valid email address.') code = 'invalid' user_regex = re.compile( - r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$" # dot-atom - r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)', # quoted-string + r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" # dot-atom + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)', # quoted-string re.IGNORECASE) domain_regex = re.compile( - r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))$', + r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z', re.IGNORECASE) literal_regex = re.compile( # literal form, ipv4 or ipv6 address (SMTP 4.1.3) - r'\[([A-f0-9:\.]+)\]$', + r'\[([A-f0-9:\.]+)\]\Z', re.IGNORECASE) domain_whitelist = ['localhost'] @@ -181,10 +184,10 @@ def __eq__(self, other): validate_email = EmailValidator() -slug_re = re.compile(r'^[-a-zA-Z0-9_]+$') +slug_re = re.compile(r'^[-a-zA-Z0-9_]+\Z') validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid') -ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') +ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z') validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid') @@ -225,7 +228,7 @@ def ip_address_validators(protocol, unpack_ipv4): raise ValueError("The protocol '%s' is unknown. Supported: %s" % (protocol, list(ip_address_validator_map))) -comma_separated_int_list_re = re.compile('^[\d,]+$') +comma_separated_int_list_re = re.compile('^[\d,]+\Z') validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid')
docs/releases/1.4.21.txt+26 −0 modified@@ -26,3 +26,29 @@ As each built-in session backend was fixed separately (rather than a fix in the core sessions framework), maintainers of third-party session backends should check whether the same vulnerability is present in their backend and correct it if so. + +Header injection possibility since validators accept newlines in input +====================================================================== + +Some of Django's built-in validators +(``django.core.validators.EmailValidator``, most seriously) didn't +prohibit newline characters (due to the usage of ``$`` instead of ``\Z`` in the +regular expressions). If you use values with newlines in HTTP response or email +headers, you can suffer from header injection attacks. Django itself isn't +vulnerable because :class:`~django.http.HttpResponse` and the mail sending +utilities in :mod:`django.core.mail` prohibit newlines in HTTP and SMTP +headers, respectively. While the validators have been fixed in Django, if +you're creating HTTP responses or email messages in other ways, it's a good +idea to ensure that those methods prohibit newlines as well. You might also +want to validate that any existing data in your application doesn't contain +unexpected newlines. + +:func:`~django.core.validators.validate_ipv4_address`, +:func:`~django.core.validators.validate_slug`, and +:class:`~django.core.validators.URLValidator` and their usage in the +corresponding form fields ``GenericIPAddresseField``, ``IPAddressField``, +``SlugField``, and ``URLField`` are also affected. + +The undocumented, internally unused ``validate_integer()`` function is now +stricter as it validates using a regular expression instead of simply casting +the value using ``int()`` and checking if an exception was raised.
docs/releases/1.7.9.txt+28 −0 modified@@ -27,6 +27,34 @@ core sessions framework), maintainers of third-party session backends should check whether the same vulnerability is present in their backend and correct it if so. +Header injection possibility since validators accept newlines in input +====================================================================== + +Some of Django's built-in validators +(``django.core.validators.EmailValidator``, most seriously) didn't +prohibit newline characters (due to the usage of ``$`` instead of ``\Z`` in the +regular expressions). If you use values with newlines in HTTP response or email +headers, you can suffer from header injection attacks. Django itself isn't +vulnerable because :class:`~django.http.HttpResponse` and the mail sending +utilities in :mod:`django.core.mail` prohibit newlines in HTTP and SMTP +headers, respectively. While the validators have been fixed in Django, if +you're creating HTTP responses or email messages in other ways, it's a good +idea to ensure that those methods prohibit newlines as well. You might also +want to validate that any existing data in your application doesn't contain +unexpected newlines. + +:func:`~django.core.validators.validate_ipv4_address`, +:func:`~django.core.validators.validate_slug`, and +:class:`~django.core.validators.URLValidator` are also affected, however, as +of Django 1.6 the ``GenericIPAddresseField``, ``IPAddressField``, ``SlugField``, +and ``URLField`` form fields which use these validators all strip the input, so +the possibility of newlines entering your data only exists if you are using +these validators outside of the form fields. + +The undocumented, internally unused ``validate_integer()`` function is now +stricter as it validates using a regular expression instead of simply casting +the value using ``int()`` and checking if an exception was raised. + Bugfixes ========
tests/validators/tests.py+14 −1 modified@@ -25,10 +25,12 @@ (validate_integer, '42', None), (validate_integer, '-42', None), (validate_integer, -42, None), - (validate_integer, -42.5, None), + (validate_integer, -42.5, ValidationError), (validate_integer, None, ValidationError), (validate_integer, 'a', ValidationError), + (validate_integer, '\n42', ValidationError), + (validate_integer, '42\n', ValidationError), (validate_email, 'email@here.com', None), (validate_email, 'weirder-email@here.and.there.com', None), @@ -66,6 +68,11 @@ (validate_email, '"\\\011"@here.com', None), (validate_email, '"\\\012"@here.com', ValidationError), (validate_email, 'trailingdot@shouldfail.com.', ValidationError), + # Trailing newlines in username or domain not allowed + (validate_email, 'a@b.com\n', ValidationError), + (validate_email, 'a\n@b.com', ValidationError), + (validate_email, '"test@test"\n@example.com', ValidationError), + (validate_email, 'a@[127.0.0.1]\n', ValidationError), (validate_slug, 'slug-ok', None), (validate_slug, 'longer-slug-still-ok', None), @@ -78,6 +85,7 @@ (validate_slug, 'some@mail.com', ValidationError), (validate_slug, '你好', ValidationError), (validate_slug, '\n', ValidationError), + (validate_slug, 'trailing-newline\n', ValidationError), (validate_ipv4_address, '1.1.1.1', None), (validate_ipv4_address, '255.0.0.0', None), @@ -87,6 +95,7 @@ (validate_ipv4_address, '25.1.1.', ValidationError), (validate_ipv4_address, '25,1,1,1', ValidationError), (validate_ipv4_address, '25.1 .1.1', ValidationError), + (validate_ipv4_address, '1.1.1.1\n', ValidationError), # validate_ipv6_address uses django.utils.ipv6, which # is tested in much greater detail in its own testcase @@ -120,6 +129,7 @@ (validate_comma_separated_integer_list, '', ValidationError), (validate_comma_separated_integer_list, 'a,b,c', ValidationError), (validate_comma_separated_integer_list, '1, 2, 3', ValidationError), + (validate_comma_separated_integer_list, '1,2,3\n', ValidationError), (MaxValueValidator(10), 10, None), (MaxValueValidator(10), -10, None), @@ -181,6 +191,9 @@ (URLValidator(), 'file://localhost/path', ValidationError), (URLValidator(), 'git://example.com/', ValidationError), (URLValidator(EXTENDED_SCHEMES), 'git://-invalid.com', ValidationError), + # Trailing newlines not accepted + (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError), + (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError), (BaseValidator(True), True, None), (BaseValidator(True), False, ValidationError),
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
21- www.debian.org/security/2015/dsa-3305nvdThird Party AdvisoryWEB
- www.oracle.com/technetwork/topics/security/bulletinoct2015-2511968.htmlnvdThird Party AdvisoryWEB
- www.ubuntu.com/usn/USN-2671-1nvdThird Party AdvisoryWEB
- github.com/advisories/GHSA-q5qw-4364-5hhmghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2015-5144ghsaADVISORY
- www.djangoproject.com/weblog/2015/jul/08/security-releases/nvdVendor Advisory
- lists.fedoraproject.org/pipermail/package-announce/2015-November/172084.htmlnvdWEB
- lists.opensuse.org/opensuse-updates/2015-10/msg00043.htmlnvdWEB
- lists.opensuse.org/opensuse-updates/2015-10/msg00046.htmlnvdWEB
- github.com/django/django/blob/4555a823fd57e261e1b19c778429473256c8ea08/docs/releases/1.4.21.txtghsaWEB
- github.com/django/django/commit/1ba1cdce7d58e6740fe51955d945b56ae51d072aghsaWEB
- github.com/django/django/commit/574dd5e0b0fbb877ae5827b1603d298edc9bb2a0ghsaWEB
- github.com/django/django/commit/8f9a4d3a2bc42f14bb437defd30c7315adbff22cghsaWEB
- github.com/django/django/commit/ae49b4d994656bc037513dcd064cb9ce5bb85649ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2015-10.yamlghsaWEB
- security.gentoo.org/glsa/201510-06nvdWEB
- web.archive.org/web/20150924150801/http://www.securitytracker.com/id/1032820ghsaWEB
- web.archive.org/web/20200228050526/http://www.securityfocus.com/bid/75665ghsaWEB
- www.djangoproject.com/weblog/2015/jul/08/security-releasesghsaWEB
- www.securityfocus.com/bid/75665nvd
- www.securitytracker.com/id/1032820nvd
News mentions
0No linked articles in our index yet.