From 001ff508081a893d0cf81df1214dbd234606c360 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
Date: Tue, 14 Mar 2017 12:33:15 -0400
Subject: [PATCH] [1.11.x] Fixed CVE-2017-7234 -- Fixed open redirect
 vulnerability in views.static.serve().

This is a security fix.
---
 django/views/static.py                | 22 ++++------------------
 docs/releases/1.10.7.txt              | 11 +++++++++++
 docs/releases/1.8.18.txt              | 11 +++++++++++
 docs/releases/1.9.13.txt              | 11 +++++++++++
 tests/view_tests/tests/test_static.py |  2 +-
 5 files changed, 38 insertions(+), 19 deletions(-)

diff --git a/django/views/static.py b/django/views/static.py
index 2af26621f3..4f62630079 100644
--- a/django/views/static.py
+++ b/django/views/static.py
@@ -12,9 +12,9 @@ import stat
 
 from django.http import (
     FileResponse, Http404, HttpResponse, HttpResponseNotModified,
-    HttpResponseRedirect,
 )
 from django.template import Context, Engine, TemplateDoesNotExist, loader
+from django.utils._os import safe_join
 from django.utils.http import http_date, parse_http_date
 from django.utils.six.moves.urllib.parse import unquote
 from django.utils.translation import ugettext as _, ugettext_lazy
@@ -36,25 +36,11 @@ def serve(request, path, document_root=None, show_indexes=False):
     but if you'd like to override it, you can create a template called
     ``static/directory_index.html``.
     """
-    path = posixpath.normpath(unquote(path))
-    path = path.lstrip('/')
-    newpath = ''
-    for part in path.split('/'):
-        if not part:
-            # Strip empty path components.
-            continue
-        drive, part = os.path.splitdrive(part)
-        head, part = os.path.split(part)
-        if part in (os.curdir, os.pardir):
-            # Strip '.' and '..' in path.
-            continue
-        newpath = os.path.join(newpath, part).replace('\\', '/')
-    if newpath and path != newpath:
-        return HttpResponseRedirect(newpath)
-    fullpath = os.path.join(document_root, newpath)
+    path = posixpath.normpath(unquote(path)).lstrip('/')
+    fullpath = safe_join(document_root, path)
     if os.path.isdir(fullpath):
         if show_indexes:
-            return directory_index(newpath, fullpath)
+            return directory_index(path, fullpath)
         raise Http404(_("Directory indexes are not allowed here."))
     if not os.path.exists(fullpath):
         raise Http404(_('"%(path)s" does not exist') % {'path': fullpath})
diff --git a/docs/releases/1.10.7.txt b/docs/releases/1.10.7.txt
index 630610e0af..0c79347cc3 100644
--- a/docs/releases/1.10.7.txt
+++ b/docs/releases/1.10.7.txt
@@ -6,6 +6,17 @@ Django 1.10.7 release notes
 
 Django 1.10.7 fixes two security issues and a bug in 1.10.6.
 
+CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()``
+=============================================================================
+
+A maliciously crafted URL to a Django site using the
+:func:`~django.views.static.serve` view could redirect to any other domain. The
+view no longer does any redirects as they don't provide any known, useful
+functionality.
+
+Note, however, that this view has always carried a warning that it is not
+hardened for production use and should be used only as a development aid.
+
 Bugfixes
 ========
 
diff --git a/docs/releases/1.8.18.txt b/docs/releases/1.8.18.txt
index 92492c7825..7b1e08046c 100644
--- a/docs/releases/1.8.18.txt
+++ b/docs/releases/1.8.18.txt
@@ -5,3 +5,14 @@ Django 1.8.18 release notes
 *April 4, 2017*
 
 Django 1.8.18 fixes two security issues in 1.8.17.
+
+CVE-2017-7234: Open redirect vulnerability in ``django.views.static.serve()``
+=============================================================================
+
+A maliciously crafted URL to a Django site using the
+:func:`~django.views.static.serve` view could redirect to any other domain. The
+view no longer does any redirects as they don't provide any known, useful
+functionality.
+
+Note, however, that this view has always carried a warning that it is not
+hardened for production use and should be used only as a development aid.
diff --git a/docs/releases/1.9.13.txt b/docs/releases/1.9.13.txt
index 42f22a8f00..f9d203eafe 100644
--- a/docs/releases/1.9.13.txt
+++ b/docs/releases/1.9.13.txt
@@ -7,6 +7,17 @@ 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-7234: Open redirect vulnerability in ``django.views.static.serve()``
+=============================================================================
+
+A maliciously crafted URL to a Django site using the
+:func:`~django.views.static.serve` view could redirect to any other domain. The
+view no longer does any redirects as they don't provide any known, useful
+functionality.
+
+Note, however, that this view has always carried a warning that it is not
+hardened for production use and should be used only as a development aid.
+
 Bugfixes
 ========
 
diff --git a/tests/view_tests/tests/test_static.py b/tests/view_tests/tests/test_static.py
index 457577264e..1f5d6e5943 100644
--- a/tests/view_tests/tests/test_static.py
+++ b/tests/view_tests/tests/test_static.py
@@ -110,7 +110,7 @@ class StaticTests(SimpleTestCase):
 
     def test_index(self):
         response = self.client.get('/%s/' % self.prefix)
-        self.assertContains(response, 'Index of /')
+        self.assertContains(response, 'Index of ./')
 
 
 class StaticHelperTest(StaticTests):