Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ae49b4d

Browse files
committed
[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.
1 parent 1828f43 commit ae49b4d

File tree

4 files changed

+83
-13
lines changed

4 files changed

+83
-13
lines changed

django/core/validators.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class URLValidator(RegexValidator):
7373
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
7474
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
7575
r'(?::\d+)?' # optional port
76-
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
76+
r'(?:/?|[/?]\S+)\Z', re.IGNORECASE)
7777
message = _('Enter a valid URL.')
7878
schemes = ['http', 'https', 'ftp', 'ftps']
7979

@@ -107,28 +107,31 @@ def __call__(self, value):
107107
else:
108108
url = value
109109

110+
integer_validator = RegexValidator(
111+
re.compile('^-?\d+\Z'),
112+
message=_('Enter a valid integer.'),
113+
code='invalid',
114+
)
115+
110116

111117
def validate_integer(value):
112-
try:
113-
int(value)
114-
except (ValueError, TypeError):
115-
raise ValidationError(_('Enter a valid integer.'), code='invalid')
118+
return integer_validator(value)
116119

117120

118121
@deconstructible
119122
class EmailValidator(object):
120123
message = _('Enter a valid email address.')
121124
code = 'invalid'
122125
user_regex = re.compile(
123-
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$" # dot-atom
124-
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)', # quoted-string
126+
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" # dot-atom
127+
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)', # quoted-string
125128
re.IGNORECASE)
126129
domain_regex = re.compile(
127-
r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))$',
130+
r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z',
128131
re.IGNORECASE)
129132
literal_regex = re.compile(
130133
# literal form, ipv4 or ipv6 address (SMTP 4.1.3)
131-
r'\[([A-f0-9:\.]+)\]$',
134+
r'\[([A-f0-9:\.]+)\]\Z',
132135
re.IGNORECASE)
133136
domain_whitelist = ['localhost']
134137

@@ -181,10 +184,10 @@ def __eq__(self, other):
181184

182185
validate_email = EmailValidator()
183186

184-
slug_re = re.compile(r'^[-a-zA-Z0-9_]+$')
187+
slug_re = re.compile(r'^[-a-zA-Z0-9_]+\Z')
185188
validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')
186189

187-
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}$')
190+
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')
188191
validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid')
189192

190193

@@ -225,7 +228,7 @@ def ip_address_validators(protocol, unpack_ipv4):
225228
raise ValueError("The protocol '%s' is unknown. Supported: %s"
226229
% (protocol, list(ip_address_validator_map)))
227230

228-
comma_separated_int_list_re = re.compile('^[\d,]+$')
231+
comma_separated_int_list_re = re.compile('^[\d,]+\Z')
229232
validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid')
230233

231234

docs/releases/1.4.21.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,29 @@ As each built-in session backend was fixed separately (rather than a fix in the
2626
core sessions framework), maintainers of third-party session backends should
2727
check whether the same vulnerability is present in their backend and correct
2828
it if so.
29+
30+
Header injection possibility since validators accept newlines in input
31+
======================================================================
32+
33+
Some of Django's built-in validators
34+
(``django.core.validators.EmailValidator``, most seriously) didn't
35+
prohibit newline characters (due to the usage of ``$`` instead of ``\Z`` in the
36+
regular expressions). If you use values with newlines in HTTP response or email
37+
headers, you can suffer from header injection attacks. Django itself isn't
38+
vulnerable because :class:`~django.http.HttpResponse` and the mail sending
39+
utilities in :mod:`django.core.mail` prohibit newlines in HTTP and SMTP
40+
headers, respectively. While the validators have been fixed in Django, if
41+
you're creating HTTP responses or email messages in other ways, it's a good
42+
idea to ensure that those methods prohibit newlines as well. You might also
43+
want to validate that any existing data in your application doesn't contain
44+
unexpected newlines.
45+
46+
:func:`~django.core.validators.validate_ipv4_address`,
47+
:func:`~django.core.validators.validate_slug`, and
48+
:class:`~django.core.validators.URLValidator` and their usage in the
49+
corresponding form fields ``GenericIPAddresseField``, ``IPAddressField``,
50+
``SlugField``, and ``URLField`` are also affected.
51+
52+
The undocumented, internally unused ``validate_integer()`` function is now
53+
stricter as it validates using a regular expression instead of simply casting
54+
the value using ``int()`` and checking if an exception was raised.

docs/releases/1.7.9.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,34 @@ core sessions framework), maintainers of third-party session backends should
2727
check whether the same vulnerability is present in their backend and correct
2828
it if so.
2929

30+
Header injection possibility since validators accept newlines in input
31+
======================================================================
32+
33+
Some of Django's built-in validators
34+
(``django.core.validators.EmailValidator``, most seriously) didn't
35+
prohibit newline characters (due to the usage of ``$`` instead of ``\Z`` in the
36+
regular expressions). If you use values with newlines in HTTP response or email
37+
headers, you can suffer from header injection attacks. Django itself isn't
38+
vulnerable because :class:`~django.http.HttpResponse` and the mail sending
39+
utilities in :mod:`django.core.mail` prohibit newlines in HTTP and SMTP
40+
headers, respectively. While the validators have been fixed in Django, if
41+
you're creating HTTP responses or email messages in other ways, it's a good
42+
idea to ensure that those methods prohibit newlines as well. You might also
43+
want to validate that any existing data in your application doesn't contain
44+
unexpected newlines.
45+
46+
:func:`~django.core.validators.validate_ipv4_address`,
47+
:func:`~django.core.validators.validate_slug`, and
48+
:class:`~django.core.validators.URLValidator` are also affected, however, as
49+
of Django 1.6 the ``GenericIPAddresseField``, ``IPAddressField``, ``SlugField``,
50+
and ``URLField`` form fields which use these validators all strip the input, so
51+
the possibility of newlines entering your data only exists if you are using
52+
these validators outside of the form fields.
53+
54+
The undocumented, internally unused ``validate_integer()`` function is now
55+
stricter as it validates using a regular expression instead of simply casting
56+
the value using ``int()`` and checking if an exception was raised.
57+
3058
Bugfixes
3159
========
3260

tests/validators/tests.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
(validate_integer, '42', None),
2626
(validate_integer, '-42', None),
2727
(validate_integer, -42, None),
28-
(validate_integer, -42.5, None),
2928

29+
(validate_integer, -42.5, ValidationError),
3030
(validate_integer, None, ValidationError),
3131
(validate_integer, 'a', ValidationError),
32+
(validate_integer, '\n42', ValidationError),
33+
(validate_integer, '42\n', ValidationError),
3234

3335
(validate_email, '[email protected]', None),
3436
(validate_email, '[email protected]', None),
@@ -66,6 +68,11 @@
6668
(validate_email, '"\\\011"@here.com', None),
6769
(validate_email, '"\\\012"@here.com', ValidationError),
6870
(validate_email, '[email protected].', ValidationError),
71+
# Trailing newlines in username or domain not allowed
72+
(validate_email, '[email protected]\n', ValidationError),
73+
(validate_email, 'a\n@b.com', ValidationError),
74+
(validate_email, '"test@test"\n@example.com', ValidationError),
75+
(validate_email, 'a@[127.0.0.1]\n', ValidationError),
6976

7077
(validate_slug, 'slug-ok', None),
7178
(validate_slug, 'longer-slug-still-ok', None),
@@ -78,6 +85,7 @@
7885
(validate_slug, '[email protected]', ValidationError),
7986
(validate_slug, '你好', ValidationError),
8087
(validate_slug, '\n', ValidationError),
88+
(validate_slug, 'trailing-newline\n', ValidationError),
8189

8290
(validate_ipv4_address, '1.1.1.1', None),
8391
(validate_ipv4_address, '255.0.0.0', None),
@@ -87,6 +95,7 @@
8795
(validate_ipv4_address, '25.1.1.', ValidationError),
8896
(validate_ipv4_address, '25,1,1,1', ValidationError),
8997
(validate_ipv4_address, '25.1 .1.1', ValidationError),
98+
(validate_ipv4_address, '1.1.1.1\n', ValidationError),
9099

91100
# validate_ipv6_address uses django.utils.ipv6, which
92101
# is tested in much greater detail in its own testcase
@@ -120,6 +129,7 @@
120129
(validate_comma_separated_integer_list, '', ValidationError),
121130
(validate_comma_separated_integer_list, 'a,b,c', ValidationError),
122131
(validate_comma_separated_integer_list, '1, 2, 3', ValidationError),
132+
(validate_comma_separated_integer_list, '1,2,3\n', ValidationError),
123133

124134
(MaxValueValidator(10), 10, None),
125135
(MaxValueValidator(10), -10, None),
@@ -181,6 +191,9 @@
181191
(URLValidator(), 'file://localhost/path', ValidationError),
182192
(URLValidator(), 'git://example.com/', ValidationError),
183193
(URLValidator(EXTENDED_SCHEMES), 'git://-invalid.com', ValidationError),
194+
# Trailing newlines not accepted
195+
(URLValidator(), 'http://www.djangoproject.com/\n', ValidationError),
196+
(URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError),
184197

185198
(BaseValidator(True), True, None),
186199
(BaseValidator(True), False, ValidationError),

0 commit comments

Comments
 (0)