Making sure Web APIs built with Django don't have CSRF vulnerabilities.

859 views
Skip to first unread message

Tom Christie

unread,
Mar 14, 2012, 2:20:01 PM3/14/12
to django-d...@googlegroups.com
Hi all,

  This is a follow on to an issue that's been discussed on django-security.  It's been suggested that it should be raised in this forum instead, so...

Most of the time when building Web APIs with Django the right thing to do is to wrap API views in @csrf_exempt.  Generally Web APIs shouldn't have CSRF, since API clients authenticate their requests explicitly, and don't need (or have) a CSRF token available to them.

That's the right thing to do except for the case where you're building a Web API that is consumed by an AJAX client of the same site.  In that case the AJAX call happens within the context of the current session, and the authentication is implicit in the request.  In the AJAX-client case, developers should be CSRF protecting their views, and setting the 'X-CSRFToken' header in their AJAX requests.

The problem comes when developers start using AJAX clients with API frameworks that CSRF exempt their views by default, and don't realize that they ought to be using CSRF protected views.  I can see at least a couple of examples of developers using AJAX clients in this way, and it seems to me that there's probably a bunch of Django sites out there at the moment that are exposing their Web APIs in a way that is vulnerable to CSRF attacks.

I think the ways we could approach this come down to one of these options:

1. It's a problem and something we should address in Django core.
2. It's a problem and something we should address in API frameworks.
3. It's a problem, and the best way to address it is to make sure developers understand that session authenticated APIs ought to have CSRF protection.
4. It's not a problem - if you're using session authenticated APIs and doing it wrong, tough luck.

It arguably isn't an issue with Django core (which does give you all the tools you need to be explicit about if and when CSRF protection runs.) or arguably in the API frameworks either (For example neither piston nor tastypie provide session based authentication by default), but does seem like a reasonably big and non-obvious pitfall.

One idea to mitigating this in Django core that I've considered would be introducing a '@csrf_defered' decorator, that would act like '@csrf_exempt', but wrap request.session in a lazily evaluated '@csrf_protect'.  That'd give developers an easy way to generically do "Don't CSRF protect this view unless/until it accesses session information.".  API frameworks could then use '@csrf_defered' instead of '@csrf_exempt', and know that they'll get the correct behavior whether the API uses session authentication or not.

I'm fairly agnostic on what approach we end up taking, but I wanted to at least raise the issue and open it to discussion.

Regards,

  Tom

Carl Meyer

unread,
Mar 14, 2012, 2:49:29 PM3/14/12
to django-d...@googlegroups.com
Hi Tom,

Thanks for raising this.

On 03/14/2012 11:20 AM, Tom Christie wrote:
[snip]


> One idea to mitigating this in Django core that I've considered would be
> introducing a '@csrf_defered' decorator, that would act like
> '@csrf_exempt', but wrap request.session in a lazily evaluated
> '@csrf_protect'. That'd give developers an easy way to generically do
> "Don't CSRF protect this view unless/until it accesses session
> information.". API frameworks could then use '@csrf_defered' instead of
> '@csrf_exempt', and know that they'll get the correct
> behavior whether the API uses session authentication or not.

This would be specific to Django's session implementation, so it would
tie the CSRF protection to use of django.contrib.sessions, which is
something we've otherwise avoided (in the new CSRF implementation). It
also wouldn't help anyone who has their own session implementation or
cookie-based authentication of some sort.

Practically speaking, I think this might be ok and would cover the
majority of real cases. But at the very least it means that this
decorator should live in contrib.sessions, not in the core CSRF code.

Carl

signature.asc

Paul McMillan

unread,
Mar 16, 2012, 11:24:04 PM3/16/12
to django-d...@googlegroups.com
>> One idea to mitigating this in Django core that I've considered would be
>> introducing a '@csrf_defered' decorator

> Practically speaking, I think this might be ok and would cover the


> majority of real cases. But at the very least it means that this
> decorator should live in contrib.sessions, not in the core CSRF code.

I would be opposed to this code in any shipped part of Django. It
certainly could be built as a third party module (if we don't have the
hooks necessary to do this, we can discuss them). My main objection is
that CSRF is not a topic which should be deferred, or maybe on, or
anything except absolutely positively explicitly on or off.
Introducing a deferred format encourages developers to ignore it,
until it causes problems, at which point they will do exactly the same
thing as they do now, and turn it off. It's easy enough to screw up
already - adding a "maybe on" is going to bite developers even more
than the current format, since it will be even easier to write code
that works most of the time, for most users, but not all of the time,
for all users.

I fall into the camp of "you should understand what you're doing when
you turn CSRF protection off". If the framework authors want to
support session based authentication, I believe they're capable of
doing it correctly. Until then, if users want to hack session based
auth onto the frameworks, they should be careful and understand what
they're doing.

For readers who have not inspected it yet, Django's CSRF
implementation is quite instructive:
https://code.djangoproject.com/browser/django/trunk/django/middleware/csrf.py

-Paul

Tom Christie

unread,
Mar 21, 2012, 8:16:06 AM3/21/12
to django-d...@googlegroups.com
I don't know how much of an issue it really is (or not), but I haven't really seen it being done right.
Of all the examples I've found of devs implementing session authentication on top of piston and tastypie, (See here, here and here) none have re-enabled CSRF protection. (As far as I can tell.)

Having said that, I really just wanted to:

1. Get the core devs opinion on if would ought to actively do anything about this in core, since it's pretty easy to shoot yourself in the foot.
2. Raise the issue, so that there's at least more awareness that devs really should be making sure that any same-site AJAX driven APIs *do* get CSRF protection. [*]

I fall into the camp of "you should understand what you're doing when
you turn CSRF protection off". If the framework authors want to
support session based authentication, I believe they're capable of
doing it correctly. Until then, if users want to hack session based
auth onto the frameworks, they should be careful and understand what
they're doing.

I'm happy with that.

Thanks for the feedback,

  Tom

[*] Only relevant to APIs that support POST/PUT or DELETE operations of course.  Read-only APIs will be just fine.
Reply all
Reply to author
Forward
0 new messages