Is this how Django's CSRF protection works? - django

Being a beginner at cookies, CSRF and Django (using 1.4), from what I can make out this is how it works, please correct me where I go wrong...
The following applies where django.middleware.csrf.CsrfViewMiddleware is included in the MIDDLEWARE_CLASSES tuple.
Where a POST form includes the csrf_token tag, and the view concerned passes RequestContext to the template, requesting the page means Django includes a hidden form field which contains an alphanumeric string. Django also returns to the browser a cookie with the name set to csrftoken and value set to the same alphanumeric string.
When receiving the form submission, Django checks that the alphanumeric string value from the hidden form field matches and the csrftoken cookie received from the browser. If they don't match a 403 response is issued.
A CSRF attack might come in the form of a malicious web site that includes an iframe. The iframe includes a POST form and some JavaScript. The form's action attribute points to my Django site. The form is designed to do something nasty at my site, and the JS submits the form when the iframe is loaded.
The browser would include the csrftoken cookie in the header of the form submission. However, the form would not include the hidden field with the matching alphanumeric string, so a 403 is returned and the attack fails. If the iframe JS tried to access the cookie, so as to create the correct hiddden form field, the browser would prevent it from doing so.
Is this correct?

I would say that you are right. You will find here my own formulation of it.
To summarize:
The CSRF token is sent from the code, which means that the malicious code must know it.
The CSRF token is stored in a cookie and sent by the browser.
The attacker cannot access the cookie because of the same-origin policy.
The server can simply verify that the "safe" value coming from the cookie is the same as the one coming from the code.

I think what you want is described here in the official Django Documentation.
https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#how-it-works
Above link was broken when I tried, but for version 1.7 this works:
https://docs.djangoproject.com/en/1.7/ref/contrib/csrf/

Related

Why does Django/Django REST Framework not validate CSRF tokens in-depth, even with enforce-CSRF?

I am trying to enforce CSRF for a Django Rest API which is open to anonymous users.
For that matter, I've tried two different approaches:
Extending the selected API views from one CSRFAPIView base view, which has an #ensure_csrf_cookie annotation on the dispatch method.
Using a custom Authentication class based on SessionAuthentication, which applies enforce_csrf() regardless of whether the user is logged in or not.
In both approaches the CSRF check seems to work superficially. In case the CSRF token is missing from the cookie or in case the length of the token is incorrect, the endpoint returns a 403 - Forbidden.
However, if I edit the value of the CSRF token in the cookie, the request is accepted without issue. So I can use a random value for CSRF, as long as it's the correct length.
This behaviour seems to deviate from the regular Django login view, in which the contents of the CSRF do matter. I am testing in local setup with debug/test_environment flags on.
What could be the reason my custom CSRF checks in DRF are not validated in-depth?
Code fragment of the custom Authentication:
class RestCsrfAuthentication(SessionAuthentication):
def authenticate(self, request):
self.enforce_csrf(request)
rotate_token(request)
return None
And in settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'csrfexample.authentication.RestCsrfAuthentication',
]
}
The specific contents of CSRF tokens in Django never matter, actually.
This reply by a Django security team member to a question similar to yours says this:
The way our CSRF tokens work is pretty simple. Each form contains a CSRF token, which matches the CSRF cookie. Before we process the protected form, we make sure that the submitted token matches the cookie. This is a server-side check, but it's not validating against a stored server-side value. Since a remote attacker should not be able to read or set arbitrary cookies on your domain, this protects you.
Since we're just matching the cookie with the posted token, the data is not sensitive (in fact it's completely arbitrary - a cookie of "zzzz" works just fine), and so the rotation/expiration recommendations don't make any difference. If an attacker can read or set arbitrary cookies on your domain, all forms of cookie-based CSRF protection are broken, full stop.
(Actually "zzzz" won't work because of length requirements, but more on that later.) I recommend reading the entire mailing list message for a fuller understanding. There are explanations there about how Django is peculiar among frameworks because CSRF protections are independent of sessions.
I found that mailing list message via this FAQ item on the Django docs:
Is posting an arbitrary CSRF token pair (cookie and POST data) a vulnerability?
No, this is by design. Without a man-in-the-middle attack, there is no way for an attacker to send a CSRF token cookie to a victim’s browser, so a successful attack would need to obtain the victim’s browser’s cookie via XSS or similar, in which case an attacker usually doesn’t need CSRF attacks.
Some security audit tools flag this as a problem but as mentioned before, an attacker cannot steal a user’s browser’s CSRF cookie. “Stealing” or modifying your own token using Firebug, Chrome dev tools, etc. isn’t a vulnerability.
(Emphasis mine.)
The message is from 2011, but it's still valid, and to prove it let's look at the code. Both Django REST Framework's SessionAuthentication and the ensure_csrf_cookie decorator use core Django's CsrfViewMiddleware (source). In that middleware class's process_view() method, you'll see that it fetches the CSRF cookie (a cookie named csrftoken by default), and then the posted CSRF token (part of the POSTed data, with a fallback to reading the X-CSRFToken header). After that, it runs _sanitize_token() on the POSTed/X-CSRFToken value. This sanitization step is where the check for the correct token length happens; this is why you're getting 403s as expected when you provide shorter or longer tokens.
After that, the method proceeds to compare the two values using the function _compare_salted_tokens(). If you read that function, and all the further calls that it makes, you'll see that it boils down to checking if the two strings match, basically without regard to the values of the strings.
This behaviour seems to deviate from the regular Django login view, in which the contents of the CSRF do matter.
No, it doesn't matter even in the built-in login views. I ran this curl command (Windows cmd format) against a mostly default Django project:
curl -v
-H "Cookie: csrftoken=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl"
-H "X-CSRFToken: abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl"
-F "username=admin" -F "password=1234" http://localhost:8000/admin/login/
and Django returned a session cookie (plus a CSRF cookie, of course).
Just a note on the way you're overriding SessionAuthentication.authenticate(): you probably already know this, but according to the DRF docs that method should return a (User, auth) tuple instead of None if the request has session data, i.e. if the request is from a logged-in user. Also, I think rotate_token() is unnecessary, because this code only checks for authentication status, and is not concerned with actually authenticating users. (The Django source says rotate_token() “should be done on login”.)

Django 1.6 HTTP_X_CSRFTOKEN header ignored if csrf cookie is missing

I have AJAX code which makes POST requests to a Django 1.6.4 application. The view has CSRF protection enabled via the django.middleware.csrf.CsrfViewMiddleware. If I do not pass a cookie but do pass the HTTP_X_CSRFTOKEN, it fails.
I am looking at the code of django.middleware.csrf.CsrfViewMiddleware and I see that on line 161 it checks to see if if csrf_token is None: after getting it from the cookie. If it is None, it returns. Only afterwards does it check the csrfmiddlewaretoken param and the HTTP_X_CSRFTOKEN request header. This looks incorrect and the check for a missing csrf_token value should only be made after checking all the possible places for where it could be found.
Any one else had similar issues? Am I seeing this incorrectly?
I think the confusion might be that the CSRF cookie and the HTTP_X_CSRFTOKEN HTTP header exist on opposite sides of the comparison. In other words, to prevent CSRF attacks, Django compares:
CSRF cookie value vs. POST token value ("csrfmiddlewaretoken")
(or)
CSRF cookie value vs. HTTP header value ("HTTP_X_CSRFTOKEN")
That's why the cookie is always necessary. Using the HTTP_X_CSRFTOKEN header is a substitute for setting the token in POST data, not a substitute for the cookie.
If you are using jQuery you can create a beforeSend function that includes the csrf token. Django CSRF for more information.
Please be aware that Django looks for the Header X-CSRFToken not HTTP_X_CSRFTOKEN.
At least that was my problem during debugging of the code. (I also checked the django.middleware.csrf.CsrfViewMiddleware for this)
The if csrf_token is None is a extra check done by Django. (Stated from the comment in the if-statement.
No CSRF cookie. For POST requests, we insist on a CSRF cookie,
and in this way we can avoid all CSRF attacks, including login
CSRF.
I think (not sure) there is no single check to only validate the header from a ajax post request,
and Django will do checks to prevent any form of CSRF attacks.

How do I log into django? (Rant warning)

Given:
A default-configured django service, and;
A default-configured django rest framework:
How do I log in?
By "default-configured", I mean that I followed the suggested tutorials of both websites.
However, neither django nor the django rest framework discusses how to use the authentication system as a user. They often seem to discuss authentication from the point of view of the python code running within the django framework.
Where can I read concise, clear documentation that tells me how the user requests the website with correct authentication?
I know that my DEFAULT_PERMISSION_CLASSES in 'REST_FRAMEWORK' is set to: rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly
Which implies that I am probably using whatever authentication is set in the django webservice by default.
And I know that my django instance has "Authenticationmiddleware" and "CsrfViewMiddleware" and the like, whatever they mean. (Ok, CSRF is easy to google, but that's besides the point)
The relevant django documentation seems to be https://docs.djangoproject.com/en/1.5/topics/auth/default/
However, that doesn't tell me how to actually log INTO the website so that I can POST some data.
Where can I find simple, concise instructions that tell me how to:
GET some data as a particular user, and.
POST some data as a particular user.
In the mean time, I will continue perusing the documentation.
So far I have attempted to do:
Obtain the csrf cookie by using GET /api-auth/login/
Perform the login by using POST /api-auth/login/ and providing the cookie obtained in the previous step.
However, Django still detects forgery.
Here is how to do it:
django-rest-framework provides a login page for you if you follow the documentation at http://django-rest-framework.org/
django by default, at least in version 1.5, uses CSRF tokens for security.
In order to login, you need to:
Obtain the cookies by visiting the login page, at /api-auth/login/, including the csrftoken and sessionid. This will be something like: csrftoken=123411231234123; sessionid=143212341234123412
Send the login page as a http POST using the above cookies, and setting the POST as a form with username, password, and csrfmiddlewaretoken as the form elements.
** The value of the csrfmiddleware should be that of the csrftoken value.
So, to re-iterate, the following must be set:
All the original cookies must be set in the POST headers.
The username in the POST form.
The password in the POST form.
The csrfmiddlewaretoken in the POST form.

CSRF protection in Django

I don't get one thing about CSRF protection in django. For example we have some malicious site. What is the problem to send get-request from this site to csrf protected url, parse the page and get csrf value, then post with this value?
For example we have some malicious site. What is the problem to send
get-request from this site to csrf protected url, parse the page and
get csrf value, then post with this value?
If you do this, the session counterpart of the CSRF cookie will not match, and your request will be rejected.
Also, it should be noted that referrer check is done only for HTTPS requests to prevent a MitM vulnerability.
See this django wiki entry for a discussion on how CSRF protection works, and this SO question that discusses the MitM attack specifically.
The main purpose of Django's CSRF is explained in the Django Docs (https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#how-it-works):
This ensures that only forms that have originated from your Web site
can be used to POST data back.
So it checks several things - cookie, referrer, posted value. And there are some limitations, that you cannot always modify all these values at your will. For example - you can set X-CSRFToken token and the POST value in an AJAX call, but the browser will not allow you to override the referrer header... You might succeed to do a successful POST using urllib2 or similar library, but this is not covered by the CSRF protection, as it is the same as you POST on a page.
Again - CSRF means Cross Site Request Forgery and it is what it protects.
REFERRER will be checked. If the REFERRER does correspond to correct URL then POSTing data is not valid.

Understanding CSRF in Django; hidden field in form and the CSRFCookie

I am writing down my understanding of the CSRF protection mechanism in Django. Please correct me if it is faulty.
The csrfViewMiddleware creates a unique string and stores it in a hidden field 'csrfmiddlewaretoken' of a form originating from the host. Since a malicious website mimicking this form will not know about the value of this field, it cannot use it.
When someone tries to POST the form, the website checks the 'csrfmiddlewaretoken' field and its value. If it is wrong or not set, then a csrf attack is detected.
But then, what exactly is the CSRFCookie? The doc says the unique value is set in CSRFCookie and also in the hidden field.This is where I am confused. Does a cookie get sent to the browser with the unique string embedded?
Django assigns an authenticated user a CSRF token that is stored in a cookie. The value in this cookie is read every time a user makes a request that is considered "unsafe" (namely POST, PUT, DELETE) in order to validate that the user, not a malicious third-party, is making the request.
The CSRF tag you place in a form actually grabs the CSRF token from the cookie and then passes it in as a POST variable when you submit a form.
With my current understanding, I am not entirely satisfied with the validated answer.
You can find my version here.
To summarize, the CSRFCookie is "safe", in the sense that the attacker cannot access it because of the same-origin policy. The browser will send this value automatically. Now, your form must also send this value (e.g. in a hidden field). This means that your form must know this value, and it can get it from the cookie.
The attacker cannot get the token from the cookie, and therefore cannot forge a malicious code that contains the token.
What is important, in the end, is that the user can send a csrf token, and that the server can verify it. Using a cookie is a convenient way of doing this, but this could be implemented differently (e.g. the server could save the CSRF tokens for each session, for instance).
I am not a specialist, but this is how I understand it. Hope it helps.