Prevented leaking the CSRF token through caching.

This is a security fix. Disclosure will follow shortly.
This commit is contained in:
Aymeric Augustin 2014-04-20 16:10:32 -04:00 committed by Tim Graham
parent 8b93b31487
commit c083e3815a
2 changed files with 36 additions and 1 deletions

View File

@ -45,7 +45,8 @@ More details about how the caching works:
from django.conf import settings from django.conf import settings
from django.core.cache import caches, DEFAULT_CACHE_ALIAS 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)
class UpdateCacheMiddleware(object): class UpdateCacheMiddleware(object):
@ -77,8 +78,15 @@ class UpdateCacheMiddleware(object):
if not self._should_update_cache(request, response): if not self._should_update_cache(request, response):
# We don't need to update the cache, just return. # We don't need to update the cache, just return.
return response return response
if response.streaming or response.status_code != 200: if response.streaming or response.status_code != 200:
return response 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- # Try to get the timeout from the "max-age" section of the "Cache-
# Control" header before reverting to using the default cache_timeout # Control" header before reverting to using the default cache_timeout
# length. # length.

27
tests/cache/tests.py vendored
View File

@ -18,11 +18,13 @@ from django.conf import settings
from django.core import management from django.core import management
from django.core.cache import (cache, caches, CacheKeyWarning, from django.core.cache import (cache, caches, CacheKeyWarning,
InvalidCacheBackendError, DEFAULT_CACHE_ALIAS) InvalidCacheBackendError, DEFAULT_CACHE_ALIAS)
from django.core.context_processors import csrf
from django.db import connection, connections, router, transaction from django.db import connection, connections, router, transaction
from django.core.cache.utils import make_template_fragment_key from django.core.cache.utils import make_template_fragment_key
from django.http import HttpResponse, StreamingHttpResponse from django.http import HttpResponse, StreamingHttpResponse
from django.middleware.cache import (FetchFromCacheMiddleware, from django.middleware.cache import (FetchFromCacheMiddleware,
UpdateCacheMiddleware, CacheMiddleware) UpdateCacheMiddleware, CacheMiddleware)
from django.middleware.csrf import CsrfViewMiddleware
from django.template import Template from django.template import Template
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.test import TestCase, TransactionTestCase, RequestFactory, override_settings from django.test import TestCase, TransactionTestCase, RequestFactory, override_settings
@ -1741,6 +1743,10 @@ def hello_world_view(request, value):
return HttpResponse('Hello World %s' % value) return HttpResponse('Hello World %s' % value)
def csrf_view(request):
return HttpResponse(csrf(request)['csrf_token'])
@override_settings( @override_settings(
CACHE_MIDDLEWARE_ALIAS='other', CACHE_MIDDLEWARE_ALIAS='other',
CACHE_MIDDLEWARE_KEY_PREFIX='middlewareprefix', CACHE_MIDDLEWARE_KEY_PREFIX='middlewareprefix',
@ -1905,6 +1911,27 @@ class CacheMiddlewareTest(TestCase):
response = other_with_prefix_view(request, '16') response = other_with_prefix_view(request, '16')
self.assertEqual(response.content, b'Hello World 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( @override_settings(
CACHE_MIDDLEWARE_KEY_PREFIX='settingsprefix', CACHE_MIDDLEWARE_KEY_PREFIX='settingsprefix',