#27958 closed Bug (invalid)
CSRF_COOKIE reset while requesting a broken relative URL over HTTPS
Reported by: | cryptogun | Owned by: | nobody |
---|---|---|---|
Component: | CSRF | Version: | 1.10 |
Severity: | Normal | Keywords: | csrf reset https 403 |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Problem: If a comment contains a broken link (under the same domain), all csrf_token are expired and any POST method would get a 403 code.
This also happen if I first open https://localhost/ and then open https://localhost/non-exist/ in another tab, now I can't make POST on the first page.
Everything was OK, while I was using HTTP.
GET https://192.168.1.2/asdf.jpg Cookie: csrftoken=BBBB ... status: 404 set-cookie: csrftoken=CCCC ------------------------------------------------------- POST https://192.168.1.2/forum/comment/bookmark/163/create/ comment_number: 1 csrfmiddlewaretoken: DDDD Cookie: csrftoken=CCCC ... status: 403
I'm using https + nginx + gunicorn.
After some debugging, I found that "CSRF_COOKIE" is not in request.META inside context_processors.py:
render(request, html404) context_processors.py get_token(request) if "CSRF_COOKIE" not in request.META: csrf_secret = _get_new_csrf_string() request.META["CSRF_COOKIE"] = _salt_cipher_secret(csrf_secret) response.set_cookie(settings.CSRF_COOKIE_NAME, request.META["CSRF_COOKIE"],
So the shared reset new csrf_cookie doesn't match with the old static html csrf_token, resulting a 403 Forbidden page.
More detail [here](https://github.com/nitely/Spirit/pull/173).
Test:
Change History (6)
follow-up: 2 comment:1 by , 8 years ago
comment:2 by , 8 years ago
Replying to Tim Graham:
I'm not sure there's a bug in Django here. Why isn't the CSRF token sent with the request for the broken relative link?
New CSRF token did sent and stored in cookie (for the broken relative link).
But manage.py check --deploy
suggest me to set this to true: CSRF_COOKIE_HTTPONLY = True
If True, client-side JavaScript is not able to access the CSRF cookie. This can help prevent malicious JavaScript from bypassing CSRF protection.
But {{ csrf_token }}
are static in pages(and ajax javascripts) that're already opened previously.
if not _compare_salted_tokens(request_csrf_token, csrf_token): return self._reject(request, REASON_BAD_TOKEN)
But why I can POST and submit comment here? djangoproject also use HTTPS.
follow-up: 4 comment:3 by , 8 years ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
If disabling CSRF_COOKIE_HTTPONLY
fixes the issue, that should be fine -- it offers little practical security, see #27611. If you want to keep it enabled, then you need to submit AJAX requests as described in #27534.
Next time, you might want to ask on our support channels unless you are really sure the issue is a bug. As far as I can tell, there's no issue here. Thanks.
comment:4 by , 8 years ago
Replying to Tim Graham:
I thought this ticket is closed and not commentable until I login now and see the input area.
If disabling CSRF_COOKIE_HTTPONLY fixes the issue
No that would not fix the issue. It's just a workaround. CSRF_COOKIE_HTTPONLY = False
I still can't make the POST.
In order to make POST I should:
- 1. Set
CSRF_COOKIE_HTTPONLY = False
- 2. Remove all {{ csrf_token }}
- 3. Add at the end of the html body:
<script> function getCookie(cname) { var name = cname + "="; var decodedCookie = decodeURIComponent(document.cookie); var ca = decodedCookie.split(';'); for(var i = 0; i <ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1); } if (c.indexOf(name) == 0) { return c.substring(name.length, c.length); } } return ""; } $('form').submit(function(event) {event.preventDefault(); newform = $(this).serialize();alert(newform + "&csrfmiddlewaretoken=" + getCookie('csrftoken')); this.submit(); return false;}) </script>
If you want to keep it enabled, then you need to submit AJAX
No. I have to disable it in order to submit AJAX successfully.
comment:5 by , 8 years ago
Sorry, but I'm still not following what you're saying. If you can submit a patch with a test, perhaps that will explain the issue more clearly.
comment:6 by , 8 years ago
My fault. I forgot to add a requires_csrf_token
decorator to my custom 404 handler. Sorry for the bothering.
Quote from Django code:
# This can be called when CsrfViewMiddleware.process_view has not run, # therefore need @requires_csrf_token in case the template needs # {% csrf_token %}. @requires_csrf_token def page_not_found(
My custom 404 handler used to be:
def handler404(request): html404 = {'general': 'homepage/404.html', 'app1': 'homepage/app1_not_found.html'} if request.path.startswith('/app1/'): response = render(request, html404['app1'], # context=RequestContext(request) ) else: response = render(request, html404['general'], # context=RequestContext(request) ) response.status_code = 404 return response
Solution 1, use built-in page_not_found
:
def handler404(request, exception): if request.path.startswith('/app1/'): template_name='homepage/app1_not_found.html' else: template_name='homepage/404.html' return page_not_found(request, exception, template_name=template_name)
Solution 2, uncommit RequestContext(request)
to get csrftoken from request. So Django won't generate a new csrftoken.
Either way solves the problem.
I learned that code from here and here, but omitted the crucial parts.
I'm not sure there's a bug in Django here. Why isn't the CSRF token sent with the request for the broken relative link?