summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2017-03-14 10:46:53 -0400
committerTim Graham <timograham@gmail.com>2017-04-04 10:42:06 -0400
commit5ea48a70afac5e5684b504f09286e7defdd1a81a (patch)
treef15a5583043ecac926b45b256091f4580d8ae5d3
parenta1f948b468b6621083a03b0d53432341b7a4d753 (diff)
Fixed #27912, CVE-2017-7233 -- Fixed is_safe_url() with numeric URLs.
This is a security fix.
-rw-r--r--django/utils/http.py57
-rw-r--r--docs/releases/1.10.7.txt12
-rw-r--r--docs/releases/1.8.18.txt12
-rw-r--r--docs/releases/1.9.13.txt12
-rw-r--r--tests/utils_tests/test_http.py3
5 files changed, 93 insertions, 3 deletions
diff --git a/django/utils/http.py b/django/utils/http.py
index bd2daad4b2..1433df4ff0 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -7,8 +7,9 @@ import warnings
from binascii import Error as BinasciiError
from email.utils import formatdate
from urllib.parse import (
- quote, quote_plus, unquote, unquote_plus, urlencode as original_urlencode,
- urlparse,
+ ParseResult, SplitResult, _coerce_args, _splitnetloc, _splitparams, quote,
+ quote_plus, scheme_chars, unquote, unquote_plus,
+ urlencode as original_urlencode, uses_params,
)
from django.core.exceptions import TooManyFieldsSent
@@ -293,12 +294,62 @@ def is_safe_url(url, host=None, allowed_hosts=None, require_https=False):
_is_safe_url(url.replace('\\', '/'), allowed_hosts, require_https=require_https))
+# Copied from urllib.parse.urlparse() but uses fixed urlsplit() function.
+def _urlparse(url, scheme='', allow_fragments=True):
+ """Parse a URL into 6 components:
+ <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
+ Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
+ Note that we don't break the components up in smaller bits
+ (e.g. netloc is a single string) and we don't expand % escapes."""
+ url, scheme, _coerce_result = _coerce_args(url, scheme)
+ splitresult = _urlsplit(url, scheme, allow_fragments)
+ scheme, netloc, url, query, fragment = splitresult
+ if scheme in uses_params and ';' in url:
+ url, params = _splitparams(url)
+ else:
+ params = ''
+ result = ParseResult(scheme, netloc, url, params, query, fragment)
+ return _coerce_result(result)
+
+
+# Copied from urllib.parse.urlsplit() with
+# https://github.com/python/cpython/pull/661 applied.
+def _urlsplit(url, scheme='', allow_fragments=True):
+ """Parse a URL into 5 components:
+ <scheme>://<netloc>/<path>?<query>#<fragment>
+ Return a 5-tuple: (scheme, netloc, path, query, fragment).
+ Note that we don't break the components up in smaller bits
+ (e.g. netloc is a single string) and we don't expand % escapes."""
+ url, scheme, _coerce_result = _coerce_args(url, scheme)
+ allow_fragments = bool(allow_fragments)
+ netloc = query = fragment = ''
+ i = url.find(':')
+ if i > 0:
+ for c in url[:i]:
+ if c not in scheme_chars:
+ break
+ else:
+ scheme, url = url[:i].lower(), url[i + 1:]
+
+ if url[:2] == '//':
+ netloc, url = _splitnetloc(url, 2)
+ if (('[' in netloc and ']' not in netloc) or
+ (']' in netloc and '[' not in netloc)):
+ raise ValueError("Invalid IPv6 URL")
+ if allow_fragments and '#' in url:
+ url, fragment = url.split('#', 1)
+ if '?' in url:
+ url, query = url.split('?', 1)
+ v = SplitResult(scheme, netloc, url, query, fragment)
+ return _coerce_result(v)
+
+
def _is_safe_url(url, allowed_hosts, require_https=False):
# Chrome considers any URL with more than two slashes to be absolute, but
# urlparse is not so flexible. Treat any url with three slashes as unsafe.
if url.startswith('///'):
return False
- url_info = urlparse(url)
+ url_info = _urlparse(url)
# Forbid URLs like http:///example.com - with a scheme, but without a hostname.
# In that URL, example.com is not the hostname but, a path component. However,
# Chrome will still consider example.com to be the hostname, so we must not
diff --git a/docs/releases/1.10.7.txt b/docs/releases/1.10.7.txt
index 0c79347cc3..c5caa65143 100644
--- a/docs/releases/1.10.7.txt
+++ b/docs/releases/1.10.7.txt
@@ -6,6 +6,18 @@ Django 1.10.7 release notes
Django 1.10.7 fixes two security issues and a bug in 1.10.6.
+CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric redirect URLs
+============================================================================================
+
+Django relies on user input in some cases (e.g.
+:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`)
+to redirect the user to an "on success" URL. The security check for these
+redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric
+URLs (e.g. ``http:999999999``) "safe" when they shouldn't be.
+
+Also, if a developer relies on ``is_safe_url()`` to provide safe redirect
+targets and puts such a URL into a link, they could suffer from an XSS attack.
+
CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()``
=============================================================================
diff --git a/docs/releases/1.8.18.txt b/docs/releases/1.8.18.txt
index 7b1e08046c..f41c7d080f 100644
--- a/docs/releases/1.8.18.txt
+++ b/docs/releases/1.8.18.txt
@@ -6,6 +6,18 @@ Django 1.8.18 release notes
Django 1.8.18 fixes two security issues in 1.8.17.
+CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric redirect URLs
+============================================================================================
+
+Django relies on user input in some cases (e.g.
+:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`)
+to redirect the user to an "on success" URL. The security check for these
+redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric
+URLs (e.g. ``http:999999999``) "safe" when they shouldn't be.
+
+Also, if a developer relies on ``is_safe_url()`` to provide safe redirect
+targets and puts such a URL into a link, they could suffer from an XSS attack.
+
CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()``
=============================================================================
diff --git a/docs/releases/1.9.13.txt b/docs/releases/1.9.13.txt
index f9d203eafe..4828096da9 100644
--- a/docs/releases/1.9.13.txt
+++ b/docs/releases/1.9.13.txt
@@ -7,6 +7,18 @@ Django 1.9.13 release notes
Django 1.9.13 fixes two security issues and a bug in 1.9.12. This is the final
release of the 1.9.x series.
+CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric redirect URLs
+============================================================================================
+
+Django relies on user input in some cases (e.g.
+:func:`django.contrib.auth.views.login` and :doc:`i18n </topics/i18n/index>`)
+to redirect the user to an "on success" URL. The security check for these
+redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric
+URLs (e.g. ``http:999999999``) "safe" when they shouldn't be.
+
+Also, if a developer relies on ``is_safe_url()`` to provide safe redirect
+targets and puts such a URL into a link, they could suffer from an XSS attack.
+
CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()``
=============================================================================
diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py
index 281fb77fbb..80e795d3af 100644
--- a/tests/utils_tests/test_http.py
+++ b/tests/utils_tests/test_http.py
@@ -97,6 +97,8 @@ class TestUtilsHttp(unittest.TestCase):
r'http://testserver\me:pass@example.com',
r'http://testserver\@example.com',
r'http:\\testserver\confirm\me@example.com',
+ 'http:999999999',
+ 'ftp:9999999999',
'\n',
)
for bad_url in bad_urls:
@@ -117,6 +119,7 @@ class TestUtilsHttp(unittest.TestCase):
'//testserver/',
'http://testserver/confirm?email=me@example.com',
'/url%20with%20spaces/',
+ 'path/http:2222222222',
)
for good_url in good_urls:
with ignore_warnings(category=RemovedInDjango21Warning):