summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2015-06-12 13:49:31 -0400
committerTim Graham <timograham@gmail.com>2015-07-08 07:35:43 -0400
commitae49b4d994656bc037513dcd064cb9ce5bb85649 (patch)
tree399eb64ae2e2f9643afbe226c03eed541e6268a4
parent1828f4341ec53a8684112d24031b767eba557663 (diff)
downloaddjango-ae49b4d994656bc037513dcd064cb9ce5bb85649.tar.gz
[1.7.x] 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.
-rw-r--r--django/core/validators.py27
-rw-r--r--docs/releases/1.4.21.txt26
-rw-r--r--docs/releases/1.7.9.txt28
-rw-r--r--tests/validators/tests.py15
4 files changed, 83 insertions, 13 deletions
diff --git a/django/core/validators.py b/django/core/validators.py
index 1e599ec765..462e3102e9 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -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,12 +107,15 @@ class URLValidator(RegexValidator):
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
@@ -120,15 +123,15 @@ 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 @@ class EmailValidator(object):
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')
diff --git a/docs/releases/1.4.21.txt b/docs/releases/1.4.21.txt
index da69b26564..477f9a722c 100644
--- a/docs/releases/1.4.21.txt
+++ b/docs/releases/1.4.21.txt
@@ -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.
diff --git a/docs/releases/1.7.9.txt b/docs/releases/1.7.9.txt
index f402a4cdd5..48a4fd3bd2 100644
--- a/docs/releases/1.7.9.txt
+++ b/docs/releases/1.7.9.txt
@@ -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
========
diff --git a/tests/validators/tests.py b/tests/validators/tests.py
index f586270040..923e64068f 100644
--- a/tests/validators/tests.py
+++ b/tests/validators/tests.py
@@ -25,10 +25,12 @@ TEST_DATA = (
(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 @@ TEST_DATA = (
(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 @@ 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),
@@ -87,6 +95,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 its own testcase
@@ -120,6 +129,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),
@@ -181,6 +191,9 @@ TEST_DATA = (
(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),