Skip to content

Commit

Permalink
[1.4.x] Prevented leaking the CSRF token through caching.
Browse files Browse the repository at this point in the history
This is a security fix. Disclosure will follow shortly.

Backport of c083e38 from master
  • Loading branch information
aaugustin authored and timgraham committed Apr 21, 2014
1 parent c1a8c42 commit 1170f28
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 1 deletion.
10 changes: 9 additions & 1 deletion django/middleware/cache.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions tests/regressiontests/cache/tests.py
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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',
Expand Down

0 comments on commit 1170f28

Please sign in to comment.