Permalink
Browse files

[1.4.x] Prevented leaking the CSRF token through caching.

This is a security fix. Disclosure will follow shortly.

Backport of c083e38 from master
  • Loading branch information...
aaugustin authored and timgraham committed Apr 20, 2014
1 parent c1a8c42 commit 1170f285ddd6a94a65f911a27788ba49ca08c0b0
Showing with 36 additions and 1 deletion.
  1. +9 −1 django/middleware/cache.py
  2. +27 −0 tests/regressiontests/cache/tests.py
View
@@ -50,7 +50,8 @@
from django.conf import settings
from django.core.cache import get_cache, 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)
class UpdateCacheMiddleware(object):
@@ -93,8 +94,15 @@ def process_response(self, request, response):
if not self._should_update_cache(request, response):
# We don't need to update the cache, just return.
return response
+
if not 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.
@@ -17,10 +17,12 @@
from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
from django.core.cache.backends.base import (CacheKeyWarning,
InvalidCacheBackendError)
+from django.core.context_processors import csrf
from django.db import router
from django.http import HttpResponse, HttpRequest, QueryDict
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
@@ -1418,6 +1420,10 @@ def hello_world_view(request, value):
return HttpResponse('Hello World %s' % value)
+def csrf_view(request):
+ return HttpResponse(csrf(request)['csrf_token'])
+
+
class CacheMiddlewareTest(TestCase):
def setUp(self):
@@ -1635,6 +1641,27 @@ def test_view_decorator(self):
response = other_with_timeout_view(request, '18')
self.assertEqual(response.content, 'Hello World 18')
+ 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))
+
CacheMiddlewareTest = override_settings(
CACHE_MIDDLEWARE_ALIAS='other',
CACHE_MIDDLEWARE_KEY_PREFIX='middlewareprefix',

0 comments on commit 1170f28

Please sign in to comment.