From e1e81aa1c4427411e3c68facdd761229ffea6f6f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 6 May 2021 08:45:23 +0200 Subject: [PATCH] Fixed #32713, Fixed CVE-2021-32052 -- Prevented newlines and tabs from being accepted in URLValidator on Python 3.9.5+. In Python 3.9.5+ urllib.parse() automatically removes ASCII newlines and tabs from URLs [1, 2]. Unfortunately it created an issue in the URLValidator. URLValidator uses urllib.urlsplit() and urllib.urlunsplit() for creating a URL variant with Punycode which no longer contains newlines and tabs in Python 3.9.5+. As a consequence, the regular expression matched the URL (without unsafe characters) and the source value (with unsafe characters) was considered valid. [1] https://bugs.python.org/issue43882 and [2] https://github.com/python/cpython/commit/76cd81d60310d65d01f9d7b48a8985d8ab89c8b4 --- django/core/validators.py | 3 +++ docs/releases/2.2.22.txt | 22 ++++++++++++++++++++++ docs/releases/3.1.10.txt | 22 ++++++++++++++++++++++ docs/releases/3.2.2.txt | 19 +++++++++++++++++-- docs/releases/index.txt | 2 ++ tests/validators/tests.py | 8 +++++++- 6 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 docs/releases/2.2.22.txt create mode 100644 docs/releases/3.1.10.txt diff --git a/django/core/validators.py b/django/core/validators.py index a385819510..f9abec602c 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -92,6 +92,7 @@ class URLValidator(RegexValidator): r'\Z', re.IGNORECASE) message = _('Enter a valid URL.') schemes = ['http', 'https', 'ftp', 'ftps'] + unsafe_chars = frozenset('\t\r\n') def __init__(self, schemes=None, **kwargs): super().__init__(**kwargs) @@ -101,6 +102,8 @@ class URLValidator(RegexValidator): def __call__(self, value): if not isinstance(value, str): raise ValidationError(self.message, code=self.code, params={'value': value}) + if self.unsafe_chars.intersection(value): + raise ValidationError(self.message, code=self.code, params={'value': value}) # Check if the scheme is valid. scheme = value.split('://')[0].lower() if scheme not in self.schemes: diff --git a/docs/releases/2.2.22.txt b/docs/releases/2.2.22.txt new file mode 100644 index 0000000000..6808a267af --- /dev/null +++ b/docs/releases/2.2.22.txt @@ -0,0 +1,22 @@ +=========================== +Django 2.2.22 release notes +=========================== + +*May 6, 2021* + +Django 2.2.22 fixes a security issue in 2.2.21. + +CVE-2021-32052: Header injection possibility since ``URLValidator`` accepted newlines in input on Python 3.9.5+ +=============================================================================================================== + +On Python 3.9.5+, :class:`~django.core.validators.URLValidator` didn't prohibit +newlines and tabs. If you used values with newlines in HTTP response, you could +suffer from header injection attacks. Django itself wasn't vulnerable because +:class:`~django.http.HttpResponse` prohibits newlines in HTTP headers. + +Moreover, the ``URLField`` form field which uses ``URLValidator`` silently +removes newlines and tabs on Python 3.9.5+, so the possibility of newlines +entering your data only existed if you are using this validator outside of the +form fields. + +This issue was introduced by the :bpo:`43882` fix. diff --git a/docs/releases/3.1.10.txt b/docs/releases/3.1.10.txt new file mode 100644 index 0000000000..e9a8fcc2d8 --- /dev/null +++ b/docs/releases/3.1.10.txt @@ -0,0 +1,22 @@ +=========================== +Django 3.1.10 release notes +=========================== + +*May 6, 2021* + +Django 3.1.10 fixes a security issue in 3.1.9. + +CVE-2021-32052: Header injection possibility since ``URLValidator`` accepted newlines in input on Python 3.9.5+ +=============================================================================================================== + +On Python 3.9.5+, :class:`~django.core.validators.URLValidator` didn't prohibit +newlines and tabs. If you used values with newlines in HTTP response, you could +suffer from header injection attacks. Django itself wasn't vulnerable because +:class:`~django.http.HttpResponse` prohibits newlines in HTTP headers. + +Moreover, the ``URLField`` form field which uses ``URLValidator`` silently +removes newlines and tabs on Python 3.9.5+, so the possibility of newlines +entering your data only existed if you are using this validator outside of the +form fields. + +This issue was introduced by the :bpo:`43882` fix. diff --git a/docs/releases/3.2.2.txt b/docs/releases/3.2.2.txt index d47da08d6c..a899bc6e29 100644 --- a/docs/releases/3.2.2.txt +++ b/docs/releases/3.2.2.txt @@ -2,9 +2,24 @@ Django 3.2.2 release notes ========================== -*Expected June 1, 2021* +*May 6, 2021* -Django 3.2.2 fixes several bugs in 3.2.1. +Django 3.2.2 fixes a security issue and a bug in 3.2.1. + +CVE-2021-32052: Header injection possibility since ``URLValidator`` accepted newlines in input on Python 3.9.5+ +=============================================================================================================== + +On Python 3.9.5+, :class:`~django.core.validators.URLValidator` didn't prohibit +newlines and tabs. If you used values with newlines in HTTP response, you could +suffer from header injection attacks. Django itself wasn't vulnerable because +:class:`~django.http.HttpResponse` prohibits newlines in HTTP headers. + +Moreover, the ``URLField`` form field which uses ``URLValidator`` silently +removes newlines and tabs on Python 3.9.5+, so the possibility of newlines +entering your data only existed if you are using this validator outside of the +form fields. + +This issue was introduced by the :bpo:`43882` fix. Bugfixes ======== diff --git a/docs/releases/index.txt b/docs/releases/index.txt index c714ea95b4..99c19b088a 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -41,6 +41,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 3.1.10 3.1.9 3.1.8 3.1.7 @@ -78,6 +79,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.22 2.2.21 2.2.20 2.2.19 diff --git a/tests/validators/tests.py b/tests/validators/tests.py index d6d013c026..09d5c40ff5 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -226,9 +226,15 @@ TEST_DATA = [ (URLValidator(), None, ValidationError), (URLValidator(), 56, ValidationError), (URLValidator(), 'no_scheme', ValidationError), - # Trailing newlines not accepted + # Newlines and tabs are not accepted. (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError), (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError), + (URLValidator(), 'http://www.djangoproject.com/\r', ValidationError), + (URLValidator(), 'http://[::ffff:192.9.5.5]\r', ValidationError), + (URLValidator(), 'http://www.django\rproject.com/', ValidationError), + (URLValidator(), 'http://[::\rffff:192.9.5.5]', ValidationError), + (URLValidator(), 'http://\twww.djangoproject.com/', ValidationError), + (URLValidator(), 'http://\t[::ffff:192.9.5.5]', ValidationError), # Trailing junk does not take forever to reject (URLValidator(), 'http://www.asdasdasdasdsadfm.com.br ', ValidationError), (URLValidator(), 'http://www.asdasdasdasdsadfm.com.br z', ValidationError),