Loading django/core/validators.py +15 −11 Original line number Diff line number Diff line Loading @@ -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): Loading Loading @@ -133,11 +133,16 @@ class URLValidator(RegexValidator): 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): Loading @@ -160,14 +165,14 @@ email_re = re.compile( 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): Loading Loading @@ -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') Loading Loading @@ -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 Original line number Diff line number Diff line Loading @@ -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 Original line number Diff line number Diff line Loading @@ -11,14 +11,17 @@ from django.utils.unittest import TestCase 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), Loading @@ -33,6 +36,11 @@ TEST_DATA = ( # 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), Loading @@ -45,6 +53,7 @@ TEST_DATA = ( (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), Loading @@ -54,6 +63,7 @@ TEST_DATA = ( (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 Loading Loading @@ -87,6 +97,7 @@ TEST_DATA = ( (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), Loading Loading @@ -138,6 +149,9 @@ TEST_DATA = ( (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), Loading Loading
django/core/validators.py +15 −11 Original line number Diff line number Diff line Loading @@ -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): Loading Loading @@ -133,11 +133,16 @@ class URLValidator(RegexValidator): 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): Loading @@ -160,14 +165,14 @@ email_re = re.compile( 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): Loading Loading @@ -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') Loading Loading @@ -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 Original line number Diff line number Diff line Loading @@ -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 Original line number Diff line number Diff line Loading @@ -11,14 +11,17 @@ from django.utils.unittest import TestCase 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), Loading @@ -33,6 +36,11 @@ TEST_DATA = ( # 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), Loading @@ -45,6 +53,7 @@ TEST_DATA = ( (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), Loading @@ -54,6 +63,7 @@ TEST_DATA = ( (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 Loading Loading @@ -87,6 +97,7 @@ TEST_DATA = ( (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), Loading Loading @@ -138,6 +149,9 @@ TEST_DATA = ( (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), Loading