Prevented newlines from being accepted in some validators.
This is a security fix; disclosure to follow shortly. Thanks to Sjoerd Job Postmus for the report and draft patch.
This commit is contained in:
parent
df049ed77a
commit
014247ad19
|
@ -83,7 +83,7 @@ class URLValidator(RegexValidator):
|
||||||
r'(?:' + ipv4_re + '|' + ipv6_re + '|' + host_re + ')'
|
r'(?:' + ipv4_re + '|' + ipv6_re + '|' + host_re + ')'
|
||||||
r'(?::\d{2,5})?' # port
|
r'(?::\d{2,5})?' # port
|
||||||
r'(?:[/?#][^\s]*)?' # resource path
|
r'(?:[/?#][^\s]*)?' # resource path
|
||||||
r'$', re.IGNORECASE)
|
r'\Z', re.IGNORECASE)
|
||||||
message = _('Enter a valid URL.')
|
message = _('Enter a valid URL.')
|
||||||
schemes = ['http', 'https', 'ftp', 'ftps']
|
schemes = ['http', 'https', 'ftp', 'ftps']
|
||||||
|
|
||||||
|
@ -125,12 +125,15 @@ class URLValidator(RegexValidator):
|
||||||
raise ValidationError(self.message, code=self.code)
|
raise ValidationError(self.message, code=self.code)
|
||||||
url = value
|
url = value
|
||||||
|
|
||||||
|
integer_validator = RegexValidator(
|
||||||
|
re.compile('^-?\d+\Z'),
|
||||||
|
message=_('Enter a valid integer.'),
|
||||||
|
code='invalid',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_integer(value):
|
def validate_integer(value):
|
||||||
try:
|
return integer_validator(value)
|
||||||
int(value)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
raise ValidationError(_('Enter a valid integer.'), code='invalid')
|
|
||||||
|
|
||||||
|
|
||||||
@deconstructible
|
@deconstructible
|
||||||
|
@ -138,16 +141,16 @@ class EmailValidator(object):
|
||||||
message = _('Enter a valid email address.')
|
message = _('Enter a valid email address.')
|
||||||
code = 'invalid'
|
code = 'invalid'
|
||||||
user_regex = re.compile(
|
user_regex = re.compile(
|
||||||
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$" # dot-atom
|
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" # dot-atom
|
||||||
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)', # quoted-string
|
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)', # quoted-string
|
||||||
re.IGNORECASE)
|
re.IGNORECASE)
|
||||||
domain_regex = re.compile(
|
domain_regex = re.compile(
|
||||||
# max length for domain name labels is 63 characters per RFC 1034
|
# max length for domain name labels is 63 characters per RFC 1034
|
||||||
r'((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))$',
|
r'((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\Z',
|
||||||
re.IGNORECASE)
|
re.IGNORECASE)
|
||||||
literal_regex = re.compile(
|
literal_regex = re.compile(
|
||||||
# literal form, ipv4 or ipv6 address (SMTP 4.1.3)
|
# literal form, ipv4 or ipv6 address (SMTP 4.1.3)
|
||||||
r'\[([A-f0-9:\.]+)\]$',
|
r'\[([A-f0-9:\.]+)\]\Z',
|
||||||
re.IGNORECASE)
|
re.IGNORECASE)
|
||||||
domain_whitelist = ['localhost']
|
domain_whitelist = ['localhost']
|
||||||
|
|
||||||
|
@ -205,14 +208,14 @@ class EmailValidator(object):
|
||||||
|
|
||||||
validate_email = EmailValidator()
|
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(
|
validate_slug = RegexValidator(
|
||||||
slug_re,
|
slug_re,
|
||||||
_("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."),
|
_("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."),
|
||||||
'invalid'
|
'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')
|
validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid')
|
||||||
|
|
||||||
|
|
||||||
|
@ -255,7 +258,7 @@ def ip_address_validators(protocol, unpack_ipv4):
|
||||||
|
|
||||||
|
|
||||||
def int_list_validator(sep=',', message=None, code='invalid'):
|
def int_list_validator(sep=',', message=None, code='invalid'):
|
||||||
regexp = re.compile('^\d+(?:%s\d+)*$' % re.escape(sep))
|
regexp = re.compile('^\d+(?:%s\d+)*\Z' % re.escape(sep))
|
||||||
return RegexValidator(regexp, message=message, code=code)
|
return RegexValidator(regexp, message=message, code=code)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
core sessions framework), maintainers of third-party session backends should
|
||||||
check whether the same vulnerability is present in their backend and correct
|
check whether the same vulnerability is present in their backend and correct
|
||||||
it if so.
|
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.
|
||||||
|
|
|
@ -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
|
check whether the same vulnerability is present in their backend and correct
|
||||||
it if so.
|
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
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
|
@ -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
|
check whether the same vulnerability is present in their backend and correct
|
||||||
it if so.
|
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
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,12 @@ TEST_DATA = [
|
||||||
(validate_integer, '42', None),
|
(validate_integer, '42', None),
|
||||||
(validate_integer, '-42', None),
|
(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, None, ValidationError),
|
||||||
(validate_integer, 'a', ValidationError),
|
(validate_integer, 'a', ValidationError),
|
||||||
|
(validate_integer, '\n42', ValidationError),
|
||||||
|
(validate_integer, '42\n', ValidationError),
|
||||||
|
|
||||||
(validate_email, 'email@here.com', None),
|
(validate_email, 'email@here.com', None),
|
||||||
(validate_email, 'weirder-email@here.and.there.com', None),
|
(validate_email, 'weirder-email@here.and.there.com', None),
|
||||||
|
@ -77,6 +79,11 @@ TEST_DATA = [
|
||||||
# Max length of domain name labels is 63 characters per RFC 1034.
|
# Max length of domain name labels is 63 characters per RFC 1034.
|
||||||
(validate_email, 'a@%s.us' % ('a' * 63), None),
|
(validate_email, 'a@%s.us' % ('a' * 63), None),
|
||||||
(validate_email, 'a@%s.us' % ('a' * 64), ValidationError),
|
(validate_email, 'a@%s.us' % ('a' * 64), 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, 'slug-ok', None),
|
||||||
(validate_slug, 'longer-slug-still-ok', None),
|
(validate_slug, 'longer-slug-still-ok', None),
|
||||||
|
@ -89,6 +96,7 @@ TEST_DATA = [
|
||||||
(validate_slug, 'some@mail.com', ValidationError),
|
(validate_slug, 'some@mail.com', ValidationError),
|
||||||
(validate_slug, '你好', ValidationError),
|
(validate_slug, '你好', ValidationError),
|
||||||
(validate_slug, '\n', ValidationError),
|
(validate_slug, '\n', ValidationError),
|
||||||
|
(validate_slug, 'trailing-newline\n', ValidationError),
|
||||||
|
|
||||||
(validate_ipv4_address, '1.1.1.1', None),
|
(validate_ipv4_address, '1.1.1.1', None),
|
||||||
(validate_ipv4_address, '255.0.0.0', None),
|
(validate_ipv4_address, '255.0.0.0', None),
|
||||||
|
@ -98,6 +106,7 @@ TEST_DATA = [
|
||||||
(validate_ipv4_address, '25.1.1.', ValidationError),
|
(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, '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
|
# validate_ipv6_address uses django.utils.ipv6, which
|
||||||
# is tested in much greater detail in its own testcase
|
# is tested in much greater detail in its own testcase
|
||||||
|
@ -142,6 +151,7 @@ TEST_DATA = [
|
||||||
|
|
||||||
(int_list_validator(sep='.'), '1.2.3', None),
|
(int_list_validator(sep='.'), '1.2.3', None),
|
||||||
(int_list_validator(sep='.'), '1,2,3', ValidationError),
|
(int_list_validator(sep='.'), '1,2,3', ValidationError),
|
||||||
|
(int_list_validator(sep='.'), '1.2.3\n', ValidationError),
|
||||||
|
|
||||||
(MaxValueValidator(10), 10, None),
|
(MaxValueValidator(10), 10, None),
|
||||||
(MaxValueValidator(10), -10, None),
|
(MaxValueValidator(10), -10, None),
|
||||||
|
@ -175,6 +185,9 @@ TEST_DATA = [
|
||||||
(URLValidator(EXTENDED_SCHEMES), 'git://example.com/', None),
|
(URLValidator(EXTENDED_SCHEMES), 'git://example.com/', None),
|
||||||
|
|
||||||
(URLValidator(EXTENDED_SCHEMES), 'git://-invalid.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), True, None),
|
||||||
(BaseValidator(True), False, ValidationError),
|
(BaseValidator(True), False, ValidationError),
|
||||||
|
|
Loading…
Reference in New Issue