Using #csrf_protect on view GET methods - django

Most of my GET requests are fired by Angular which makes it easy to set the CSRF Token in the header. This already works out of the box for POST requests, however I was wondering whether I could use CSRF protection for GET requests as well.
My initial instinct was to add #csrf_protect before the View's get method, for instance:
class ProjectView(View):
#csrf_protect
def get(self, request, *args, **kwargs):
...
However this produces the error:
AttributeError at /project
'ProjectView' object has no attribute 'COOKIES'
File "c:\Apps\msysgit\simpletask\lib\site-packages\django\middleware\csrf.py" in process_view
95. request.COOKIES[settings.CSRF_COOKIE_NAME])
So I'm guessing there's something which a POST request provides which a GET request does not. Is there a better way to do this?
Also, before anybody mentions that all sensitive information should be pulled via POST requests (as I've seen a few times lately), my reason for trying to stick with GET is I'm trying to build according to RESTful guidelines, meaning GET requests are for pulling data, POST is for new records, PUT is for updates and DELETE is for data deletion.
EDIT:
Since there seems to be a sentiment that I shouldn't be attempting the above, while I do now agree somewhat, the exploit(s) I'm attempting to prevent are discussed in these 3 links:
http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
http://haacked.com/archive/2009/06/24/json-hijacking.aspx
http://balpha.de/2013/02/plain-text-considered-harmful-a-cross-domain-exploit/

I don't think you quite understand what CSRF actually protects. CSRF exploits the trust that a site has in a user's browser; by exploiting a session cookie that allows the attacker to run commands on behalf of the user.
The site trusts the request because it hasn't expired the session cookie for the browser. This is why in most CSRF mitigation techniques a unique OTP for each request that has a side effect is generated. The attacker would not know the key generated for the OTP, and hence cannot forge a legitimate request.
For all URLs that have a side effect (like POST, PUT, DELETE) you would need CSRF. For GET it doesn't make any sense to have it, since GET requests - if you are doing REST correctly - should be idempotent - if run multiple times, they return the same results and they should not have any side effects.
CSRF protection does not prevent information disclosure or man in the middle attacks. So if you have sensitive information you want to protect, use HTTPS. To prevent unregulated use of your API, use authentication and authorization.

You need to use method_decorator
https://docs.djangoproject.com/en/1.4/topics/class-based-views/#decorating-the-class
So your code will be
class ProjectView(View):`
#method_decorator(csrf_protect)
def get(self, request, *args, **kwargs):
[EDIT]
However csrf_protect will check only POST requests (it has `if request.method == 'POST' check) so you have to implement something by your own.

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 session on mobile applications

We are currently developing a mobile application(using ionic) for our site that was built using Django. We used django sessions on the site everytime a user logs in. From what I understand, django session sets the session id in the client that is stored in the cookie of the browser. How can we set this session id in the mobile app, if the mobile app is separate from django?
I see at least three ways you can approach this:
find a way to get your ionic app to work with django's cookies (session and CSRF).
change your django app in three ways: 1) have login/logout views that give an authentication token to the mobile application, 2) extend SessionMiddleware to extract the session id from the authentication token sent via the "Authorization" HTTP header of the mobile app's requests, 3) extend django's CSRF middleware to make requests that do not come from web browsers exempt from CSRF checks.
try to use existing solutions for adding JWT (Json Web Token) authentication to Django.
Getting WebView-based hybrid mobile apps to work properly with cookies on both android and ios seems to be non-trivial (e.g. Handling cookies in PhoneGap/Cordova).
The existing solutions for JWT-auth I found thus far don't seem to use Django's sessions (which I find rather convenient for the sake of having a uniform solution for killing the sessions of users who get their phones/computers stolen or their accounts hacked). If someone knows of a pluggable solution that does token-authentication on top of django's regular sessions, please comment.
To elaborate on method 2:
When your mobile app doesn't handle cookies, it can't even log in due to Django's CSRF check. That's why requests coming from your mobile app need to be exempt from CSRF protection.
Note: Do NOT simply disable CSRF protection. While the mobile application isn't vulnerable to CSRF attacks, the browsers of people who are visiting the existing website still are.
To extend the CSRF mechanism to exempt non-browser requests, you need an effective way to determine whether a request is coming from a web browser. There's a bunch of HTTP headers (E.g. Referer, Cookie, X-Requested-With, Origin) we can look at to get an idea. Let's assume for now that your legitimate website users don't spoof their headers.
Use the same "is-web-browser"-check you used for CSRF-exemption to protect the mobile app's login/logout views.
Example Code (for an Android mobile app, iOS headers coming from the mobile app are likely different):
def is_mobile_app_access(request):
return request.META.get('HTTP_REFERER', None) is None
and request.META.get('HTTP_COOKIE', None) is None
and request.META.get('HTTP_X_REQUESTED_WITH', None) == 'your.app.name.here'
and request.META.get('HTTP_ORIGIN', None) == 'file://'
class CustomCsrfViewMiddleware(CsrfViewMiddleware):
def process_view(self, request, callback, callback_args, callback_kwargs):
if is_mobile_app_access(request):
return None
else:
return super(CustomCsrfViewMiddleware, self).process_view(request, callback, callback_args, callback_kwargs)
def process_response(self, request, response):
if is_mobile_app_access(request):
return response
else:
return super(CustomCsrfViewMiddleware, self).process_response(request, response)
class CustomSessionMiddleware(SessionMiddleware):
def process_request(self, request):
if is_mobile_app_access(request):
session_key = request.META.get("HTTP_AUTHORIZATION", None)
else:
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
If anyone sees something fundamentally wrong with this approach, please let me know. I suppose the weakest link in this approach is how reliable the is_mobile_app_access check is.
What did you end up doing?

Django: CSRF check only when certain conditions apply

I have a simple REST API that needs to be accessible both from a web app as well as from a remote service.
The remote service is authenticated via a custom HTTP header containing an API key.
How can I protect that API such that requests from a web browser are CSRF protected, but the CSRF check is not done when authenticated via API key? Or, in general, how can I enable CSRF protection for some requests on a specific view, but not others?
Currently, I have a decorator that checks the request for an API key and authenticates the API user roughly this way:
# Regular auth
if request.user.is_authenticated():
# DO CSRF verification, then continue calling the view
elif 'HTTP_X_API_KEY' in request.META:
api_key = request.META['HTTP_X_API_KEY']
user = authenticate(username=settings.API_USER_NAME, password=api_key)
login(request, user)
# If user is authenticated and autzorized, continue calling the view
# but WITHOUT invoking CSRF protection
The problem as stated is, that I only want CSRF protection for regular users, but not for the API user.
Okay, so after a bit more of tinkering around, the solution was to disable the CSRF middleware and enable csrf_protect for all cases where CSRF protection is required.
This works in the special case of the API, as every call is being decorated anyway, so the risk of some view being forgotten is negligible.
What does not work however, is the other way around, using csrf_exempt. The problem there is that the decorator only sets a csrf_exempt property on the view, which, if you have multiple decorators, may be masked again, even if you use functools.wraps.
Also, since csrf_exempt puts a property on the view, it cannot be dynamically enabled or disabled based on the request's content - it's really a very static thing to do.

In what case can CSRF-exempt be dangerous?

This question is more a re-insurance than one directly about how to code. As an autodidact i did not have a lot of possibilities to ask professionals such things, so i try here.
I have read the documents in the django-docs ( https://docs.djangoproject.com/en/1.3/ref/contrib/csrf/ ) and some info on that page: http://cwe.mitre.org/top25/#CWE-352
As far as i have understood, django delivers a token (some kind of pin-code) to a user. And to verify it really is him, he has to return it the next time he does a request. And some guys at Google found out that this is even possible with ajax-requests, which is why we have the new policy of protecting them too since 1.2.6. And CSRF is about someone giving me something (bad, dangerous code, corrupt files or something like that) pretending to be someone else.
So if i have some code like this:
#csrf_exempt
def grab(request):
"""
view to download an item
POST because it stores that a user has downloaded this item
"""
item_id = request.POST.get('item', None)
if not loop: return HttpResponseBadRequest('no item id provided')
item = Item.objects.get(pk=int(item_id))
that should be save, as i'm not giving access to the database or any part of my application before trying to convert the given value to an integer. And there is not too much damage if i do a wrong record of someone downloading a file (in this case it is almost none). Assuming i would write bills relying on this view, the CSRF-exempt would be vary bad idea (Is that right?).
I also do not understand why somebody can't steal the CSRF-token from a user and use it to still trick me (or the user). So i have some questions about this topic:
1) are my assumptions from above right?
2) can somebody tell me, what (and probably how) some not so nice guy could use the view above to do dirty tricks, and what would they be?
3) is a CSRF an example of a man-in-the-middle attack, is it just related to it, or is it something entirely different?
4) Any valuable links to do further reading on such dangers?
Maybe some of these questions sound no too well informed, but i'm trying to get over that. I would be very glad if someone could help me.
CSRF attacks are about forcing a victims browser to send forged requests. A simple <img> or automatically submitted <form> suffice to do this for both GET and POST method. And as the requests are send by the browser, it sends any authentication credentials along and thus making the requests seem authentic and legitimate from the server’s point of view as they basically don’t differ from those initiated by the user’s actions.
And that’s exactly what the CSRF token is used for: establish a difference between requests that were initiated by the user and those that were forged by a third party site. For this purpose the CSRF token acts as a secret that is only known to the server and the user. The server puts the secret in the document in a response and expects it to be send back in the next request.
And as the secret is embedded in the response’s document that is assigned for this specific user, an attacker would need to eavesdrop that specific response or access the document in some other way. There certainly are attacks get the CSRF token (e. g. eavesdropping, MITM, XSS, etc.). But if you are protected against those attacks, an attacker won’t be able to forge an authentic request.
CSRF attack
I trick you into viewing a webpage where I inserted some code (a request, typically through img or form) to another site (where you possibly have some rights).
Innocuous example
<img src="http://www.yoursite.net/changelanguage?lang=fr">
I cruelly changed the language of your session to french. Oh noes! You can safely remove csrf protection and save a db hit.
Dangerous examples
<img src="http://www.yourbank.net/sendmoney?amt=9999&account=123>
If you were logged in in yourbank.net (and it has no csrf or any other protection), your account should feel lighter by now. (I am 123.)
<img src="http://www.yoursite.net/admin/users/123/edit?admin=1">
If you were logged in in yoursite.net as an admin, then we both are. (I am 123.)

Django Middleware + URL's

Can a middleware check to see if a value is in the url, such as an image id ("/image/152/"), and if it is then do some checks to make sure the current user has permission to view that image and if not redirect to another url?
I had to roll my own permissions for this site I am working on and I don't want to clog up almost every view I write for the whole site with the same code, so I thought a middleware would be a good idea for this, but I'm not sure how to go about doing it.
Yes, this is possible. The django middleware docs for process_request indicate that:
def process_request(self, request)
request is an HttpRequest object. This method is called on each request, before Django decides which view to execute.
process_request() should return either None or an HttpResponse object. If it returns None, Django will continue processing this request, executing any other middleware and, then, the appropriate view. If it returns an HttpResponse object, Django won't bother calling ANY other request, view or exception middleware, or the appropriate view; it'll return that HttpResponse.
The HttpRequest object has a path attribute that will give you the URL that was requested.
If you prefer, however, note that you can also extend Django's system for authentication backends to populate the user in the request with permissions based on any arbitrary criteria, such as perhaps your hand-rolled permissions scheme. This way, you can leverage the default authentication decorators (#permission_required and #user_passes_test), and other apps/the admin site will be able to honour your permissions as well. The User object and permissions created do not need to reside in Django's user/permission tables, and can be created virtually on login; I've had a fair amount of success with this.
See Writing an authentication backend if this appeals.
If you implement authorization (Permission system) in middleware, you will end up with two options:
Check URL and allow to access
Check URL and reject access
If your requirement is that much simple, it is fine, since you don't have to touch views.
But in general, Permission system is much complex than that, for example:
User can access FOO/show_all/
But, he can't see or access foo instance, i.e, FOO/show/foo_1/
Since, he can't access foo_1 instance, we should not show them in show_all
(all foo instances)
If you want implement above 3 together, I suggest you write your own custom authorization backend for Django. All you need to do is implement few methods (your specific logic) and attach as backend.