summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAymeric Augustin <aymeric.augustin@m4x.org>2014-04-20 16:24:53 -0400
committerTim Graham <timograham@gmail.com>2014-04-21 18:29:24 -0400
commit380545bf85cbf17fc698d136815b7691f8d023ca (patch)
tree0eb49daaed26e93f3fc57c185baa232ea4befeb8
parent546740544d7f69254a67b06a3fc7fa0c43512958 (diff)
[1.7.x] Prevented leaking the CSRF token through caching.
This is a security fix. Disclosure will follow shortly. Backport of c083e3815aec23b99833da710eea574e6f2e8566 from master
-rw-r--r--django/middleware/cache.py10
-rw-r--r--tests/cache/tests.py27
2 files changed, 36 insertions, 1 deletions
diff --git a/django/middleware/cache.py b/django/middleware/cache.py
index 1c400fe605..1301d8f6a3 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
@@ -47,7 +47,8 @@ import warnings
from django.conf import settings
from django.core.cache import caches, DEFAULT_CACHE_ALIAS
-from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age
+from django.utils.cache import (get_cache_key, get_max_age, has_vary_header,
+ learn_cache_key, patch_response_headers)
from django.utils.deprecation import RemovedInDjango18Warning
@@ -91,8 +92,15 @@ class UpdateCacheMiddleware(object):
if not self._should_update_cache(request, response):
# We don't need to update the cache, just return.
return response
+
if response.streaming or response.status_code != 200:
return response
+
+ # Don't cache responses that set a user-specific (and maybe security
+ # sensitive) cookie in response to a cookie-less request.
+ if not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie'):
+ return response
+
# Try to get the timeout from the "max-age" section of the "Cache-
# Control" header before reverting to using the default cache_timeout
# length.
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index 601d5c2b2b..c93bcd816a 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -18,11 +18,13 @@ from django.conf import settings
from django.core import management
from django.core.cache import (cache, caches, CacheKeyWarning,
InvalidCacheBackendError, DEFAULT_CACHE_ALIAS)
+from django.core.context_processors import csrf
from django.db import connection, router, transaction
from django.core.cache.utils import make_template_fragment_key
from django.http import HttpResponse, StreamingHttpResponse
from django.middleware.cache import (FetchFromCacheMiddleware,
UpdateCacheMiddleware, CacheMiddleware)
+from django.middleware.csrf import CsrfViewMiddleware
from django.template import Template
from django.template.response import TemplateResponse
from django.test import TestCase, TransactionTestCase, RequestFactory, override_settings
@@ -1739,6 +1741,10 @@ def hello_world_view(request, value):
return HttpResponse('Hello World %s' % value)
+def csrf_view(request):
+ return HttpResponse(csrf(request)['csrf_token'])
+
+
@override_settings(
CACHE_MIDDLEWARE_ALIAS='other',
CACHE_MIDDLEWARE_KEY_PREFIX='middlewareprefix',
@@ -1958,6 +1964,27 @@ class CacheMiddlewareTest(IgnoreDeprecationWarningsMixin, TestCase):
response = other_with_prefix_view(request, '16')
self.assertEqual(response.content, b'Hello World 16')
+ def test_sensitive_cookie_not_cached(self):
+ """
+ Django must prevent caching of responses that set a user-specific (and
+ maybe security sensitive) cookie in response to a cookie-less request.
+ """
+ csrf_middleware = CsrfViewMiddleware()
+ cache_middleware = CacheMiddleware()
+
+ request = self.factory.get('/view/')
+ self.assertIsNone(cache_middleware.process_request(request))
+
+ csrf_middleware.process_view(request, csrf_view, (), {})
+
+ response = csrf_view(request)
+
+ response = csrf_middleware.process_response(request, response)
+ response = cache_middleware.process_response(request, response)
+
+ # Inserting a CSRF cookie in a cookie-less request prevented caching.
+ self.assertIsNone(cache_middleware.process_request(request))
+
@override_settings(
CACHE_MIDDLEWARE_KEY_PREFIX='settingsprefix',