Skip to content

Commit 4284732

Browse files
apollo13timgraham
authored andcommitted
[1.11.x] Fixed #28488 -- Reallowed error handlers to access CSRF tokens.
Regression in eef95ea. Backport of c4c128d from master
1 parent 19ea298 commit 4284732

File tree

5 files changed

+57
-6
lines changed

5 files changed

+57
-6
lines changed

django/middleware/csrf.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,16 @@ def _set_token(self, request, response):
201201
# Set the Vary header since content varies with the CSRF cookie.
202202
patch_vary_headers(response, ('Cookie',))
203203

204-
def process_view(self, request, callback, callback_args, callback_kwargs):
205-
if getattr(request, 'csrf_processing_done', False):
206-
return None
207-
204+
def process_request(self, request):
208205
csrf_token = self._get_token(request)
209206
if csrf_token is not None:
210207
# Use same token next time.
211208
request.META['CSRF_COOKIE'] = csrf_token
212209

210+
def process_view(self, request, callback, callback_args, callback_kwargs):
211+
if getattr(request, 'csrf_processing_done', False):
212+
return None
213+
213214
# Wait until request.META["CSRF_COOKIE"] has been manipulated before
214215
# bailing out, so that get_token still works
215216
if getattr(callback, 'csrf_exempt', False):
@@ -285,6 +286,7 @@ def process_view(self, request, callback, callback_args, callback_kwargs):
285286
reason = REASON_BAD_REFERER % referer.geturl()
286287
return self._reject(request, reason)
287288

289+
csrf_token = request.META.get('CSRF_COOKIE')
288290
if csrf_token is None:
289291
# No CSRF cookie. For POST requests, we insist on a CSRF cookie,
290292
# and in this way we can avoid all CSRF attacks, including login

docs/releases/1.11.6.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ Bugfixes
1414

1515
* Fixed crash when using the name of a model's autogenerated primary key
1616
(``id``) in an ``Index``'s ``fields`` (:ticket:`28597`).
17+
18+
* Fixed a regression in Django 1.9 where a custom view error handler such as
19+
``handler404`` that accesses ``csrf_token`` could cause CSRF verification
20+
failures on other pages (:ticket:`28488`).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
urlpatterns = []
2+
3+
handler404 = 'csrf_tests.views.csrf_token_error_handler'

tests/csrf_tests/tests.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def test_process_response_get_token_not_used(self):
9090
# does use the csrf request processor. By using this, we are testing
9191
# that the view processor is properly lazy and doesn't call get_token()
9292
# until needed.
93+
self.mw.process_request(req)
9394
self.mw.process_view(req, non_token_view_using_request_processor, (), {})
9495
resp = non_token_view_using_request_processor(req)
9596
resp2 = self.mw.process_response(req, resp)
@@ -105,6 +106,7 @@ def test_process_request_no_csrf_cookie(self):
105106
"""
106107
with patch_logger('django.security.csrf', 'warning') as logger_calls:
107108
req = self._get_POST_no_csrf_cookie_request()
109+
self.mw.process_request(req)
108110
req2 = self.mw.process_view(req, post_form_view, (), {})
109111
self.assertEqual(403, req2.status_code)
110112
self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_NO_CSRF_COOKIE)
@@ -116,6 +118,7 @@ def test_process_request_csrf_cookie_no_token(self):
116118
"""
117119
with patch_logger('django.security.csrf', 'warning') as logger_calls:
118120
req = self._get_POST_csrf_cookie_request()
121+
self.mw.process_request(req)
119122
req2 = self.mw.process_view(req, post_form_view, (), {})
120123
self.assertEqual(403, req2.status_code)
121124
self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_BAD_TOKEN)
@@ -125,6 +128,7 @@ def test_process_request_csrf_cookie_and_token(self):
125128
If both a cookie and a token is present, the middleware lets it through.
126129
"""
127130
req = self._get_POST_request_with_token()
131+
self.mw.process_request(req)
128132
req2 = self.mw.process_view(req, post_form_view, (), {})
129133
self.assertIsNone(req2)
130134

@@ -134,6 +138,7 @@ def test_process_request_csrf_cookie_no_token_exempt_view(self):
134138
has been applied to the view, the middleware lets it through
135139
"""
136140
req = self._get_POST_csrf_cookie_request()
141+
self.mw.process_request(req)
137142
req2 = self.mw.process_view(req, csrf_exempt(post_form_view), (), {})
138143
self.assertIsNone(req2)
139144

@@ -143,6 +148,7 @@ def test_csrf_token_in_header(self):
143148
"""
144149
req = self._get_POST_csrf_cookie_request()
145150
req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id
151+
self.mw.process_request(req)
146152
req2 = self.mw.process_view(req, post_form_view, (), {})
147153
self.assertIsNone(req2)
148154

@@ -153,6 +159,7 @@ def test_csrf_token_in_header_with_customized_name(self):
153159
"""
154160
req = self._get_POST_csrf_cookie_request()
155161
req.META['HTTP_X_CSRFTOKEN_CUSTOMIZED'] = self._csrf_id
162+
self.mw.process_request(req)
156163
req2 = self.mw.process_view(req, post_form_view, (), {})
157164
self.assertIsNone(req2)
158165

@@ -181,12 +188,14 @@ def test_put_and_delete_allowed(self):
181188
req = self._get_GET_csrf_cookie_request()
182189
req.method = 'PUT'
183190
req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id
191+
self.mw.process_request(req)
184192
req2 = self.mw.process_view(req, post_form_view, (), {})
185193
self.assertIsNone(req2)
186194

187195
req = self._get_GET_csrf_cookie_request()
188196
req.method = 'DELETE'
189197
req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id
198+
self.mw.process_request(req)
190199
req2 = self.mw.process_view(req, post_form_view, (), {})
191200
self.assertIsNone(req2)
192201

@@ -220,6 +229,7 @@ def test_token_node_with_csrf_cookie(self):
220229
CsrfTokenNode works when a CSRF cookie is set.
221230
"""
222231
req = self._get_GET_csrf_cookie_request()
232+
self.mw.process_request(req)
223233
self.mw.process_view(req, token_view, (), {})
224234
resp = token_view(req)
225235
self._check_token_present(resp)
@@ -229,6 +239,7 @@ def test_get_token_for_exempt_view(self):
229239
get_token still works for a view decorated with 'csrf_exempt'.
230240
"""
231241
req = self._get_GET_csrf_cookie_request()
242+
self.mw.process_request(req)
232243
self.mw.process_view(req, csrf_exempt(token_view), (), {})
233244
resp = token_view(req)
234245
self._check_token_present(resp)
@@ -260,6 +271,7 @@ def test_cookie_not_reset_on_accepted_request(self):
260271
requests. If it appears in the response, it should keep its value.
261272
"""
262273
req = self._get_POST_request_with_token()
274+
self.mw.process_request(req)
263275
self.mw.process_view(req, token_view, (), {})
264276
resp = token_view(req)
265277
resp = self.mw.process_response(req, resp)
@@ -333,6 +345,7 @@ def test_https_good_referer(self):
333345
req._is_secure_override = True
334346
req.META['HTTP_HOST'] = 'www.example.com'
335347
req.META['HTTP_REFERER'] = 'https://www.example.com/somepage'
348+
self.mw.process_request(req)
336349
req2 = self.mw.process_view(req, post_form_view, (), {})
337350
self.assertIsNone(req2)
338351

@@ -347,6 +360,7 @@ def test_https_good_referer_2(self):
347360
req._is_secure_override = True
348361
req.META['HTTP_HOST'] = 'www.example.com'
349362
req.META['HTTP_REFERER'] = 'https://www.example.com'
363+
self.mw.process_request(req)
350364
req2 = self.mw.process_view(req, post_form_view, (), {})
351365
self.assertIsNone(req2)
352366

@@ -360,6 +374,7 @@ def _test_https_good_referer_behind_proxy(self):
360374
'HTTP_X_FORWARDED_HOST': 'www.example.com',
361375
'HTTP_X_FORWARDED_PORT': '443',
362376
})
377+
self.mw.process_request(req)
363378
req2 = self.mw.process_view(req, post_form_view, (), {})
364379
self.assertIsNone(req2)
365380

@@ -373,6 +388,7 @@ def test_https_csrf_trusted_origin_allowed(self):
373388
req._is_secure_override = True
374389
req.META['HTTP_HOST'] = 'www.example.com'
375390
req.META['HTTP_REFERER'] = 'https://dashboard.example.com'
391+
self.mw.process_request(req)
376392
req2 = self.mw.process_view(req, post_form_view, (), {})
377393
self.assertIsNone(req2)
378394

@@ -386,6 +402,7 @@ def test_https_csrf_wildcard_trusted_origin_allowed(self):
386402
req._is_secure_override = True
387403
req.META['HTTP_HOST'] = 'www.example.com'
388404
req.META['HTTP_REFERER'] = 'https://dashboard.example.com'
405+
self.mw.process_request(req)
389406
response = self.mw.process_view(req, post_form_view, (), {})
390407
self.assertIsNone(response)
391408

@@ -394,6 +411,7 @@ def _test_https_good_referer_matches_cookie_domain(self):
394411
req._is_secure_override = True
395412
req.META['HTTP_REFERER'] = 'https://foo.example.com/'
396413
req.META['SERVER_PORT'] = '443'
414+
self.mw.process_request(req)
397415
response = self.mw.process_view(req, post_form_view, (), {})
398416
self.assertIsNone(response)
399417

@@ -403,6 +421,7 @@ def _test_https_good_referer_matches_cookie_domain_with_different_port(self):
403421
req.META['HTTP_HOST'] = 'www.example.com'
404422
req.META['HTTP_REFERER'] = 'https://foo.example.com:4443/'
405423
req.META['SERVER_PORT'] = '4443'
424+
self.mw.process_request(req)
406425
response = self.mw.process_view(req, post_form_view, (), {})
407426
self.assertIsNone(response)
408427

@@ -467,11 +486,13 @@ def _set_post(self, post):
467486
token = ('ABC' + self._csrf_id)[:CSRF_TOKEN_LENGTH]
468487

469488
req = CsrfPostRequest(token, raise_error=False)
489+
self.mw.process_request(req)
470490
resp = self.mw.process_view(req, post_form_view, (), {})
471491
self.assertIsNone(resp)
472492

473493
req = CsrfPostRequest(token, raise_error=True)
474494
with patch_logger('django.security.csrf', 'warning') as logger_calls:
495+
self.mw.process_request(req)
475496
resp = self.mw.process_view(req, post_form_view, (), {})
476497
self.assertEqual(resp.status_code, 403)
477498
self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_BAD_TOKEN)
@@ -609,6 +630,7 @@ def test_bare_secret_accepted_and_replaced(self):
609630
The csrf token is reset from a bare secret.
610631
"""
611632
req = self._get_POST_bare_secret_csrf_cookie_request_with_token()
633+
self.mw.process_request(req)
612634
req2 = self.mw.process_view(req, token_view, (), {})
613635
self.assertIsNone(req2)
614636
resp = token_view(req)
@@ -680,7 +702,7 @@ def test_no_session_on_request(self):
680702
'SessionMiddleware must appear before CsrfViewMiddleware in MIDDLEWARE.'
681703
)
682704
with self.assertRaisesMessage(ImproperlyConfigured, msg):
683-
self.mw.process_view(HttpRequest(), None, (), {})
705+
self.mw.process_request(HttpRequest())
684706

685707
def test_process_response_get_token_used(self):
686708
"""The ensure_csrf_cookie() decorator works without middleware."""
@@ -754,3 +776,16 @@ def test_https_reject_insecure_referer(self):
754776
'Referer checking failed - Referer is insecure while host is secure.',
755777
status_code=403,
756778
)
779+
780+
781+
@override_settings(ROOT_URLCONF='csrf_tests.csrf_token_error_handler_urls', DEBUG=False)
782+
class CsrfInErrorHandlingViewsTests(SimpleTestCase):
783+
def test_csrf_token_on_404_stays_constant(self):
784+
response = self.client.get('/does not exist/')
785+
# The error handler returns status code 599.
786+
self.assertEqual(response.status_code, 599)
787+
token1 = response.content
788+
response = self.client.get('/does not exist/')
789+
self.assertEqual(response.status_code, 599)
790+
token2 = response.content
791+
self.assertTrue(equivalent_tokens(token1.decode('ascii'), token2.decode('ascii')))

tests/csrf_tests/views.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from __future__ import unicode_literals
22

33
from django.http import HttpResponse
4-
from django.template import RequestContext, Template
4+
from django.middleware.csrf import get_token
5+
from django.template import Context, RequestContext, Template
56
from django.template.context_processors import csrf
67
from django.views.decorators.csrf import ensure_csrf_cookie
78

@@ -30,3 +31,9 @@ def non_token_view_using_request_processor(request):
3031
context = RequestContext(request, processors=[csrf])
3132
template = Template('')
3233
return HttpResponse(template.render(context))
34+
35+
36+
def csrf_token_error_handler(request, **kwargs):
37+
"""This error handler accesses the CSRF token."""
38+
template = Template(get_token(request))
39+
return HttpResponse(template.render(Context()), status=599)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy