Django CSRF verification failed in IE IFRAME

I ran into an interesting issue with the Django's CSRF (cross site request forgery) protection this week. Some users were reporting seeing the dreaded "CSRF verification failed. Request aborted." error trying to submit a particular form. Unable to reproduce the issue myself, we finally learned that this was happening when coming from LinkedIn.

Forbidden. CSRF verification failed. Request aborted.

Digging deeper, it turns out only to happen in IE (6/7/8/9), and only in an IFRAME, like the one LinkedIn is using for their framebar. After some research, it was revealed that IE6 added a security feature to block all third party cookies by default, which includes any pages in an IFRAME on a different domain than the top level parent. There are a few options for work-arounds.

Work around #1: Framebusting

You can use javascript to "break out" of the IFRAME, essentially replacing the top level window with yourself. There are various techniques, but I like the simple:

if(top != self) top.location.replace(location);

However, this won't work if you want to allow IFRAMEs in somecases, say from your own domain(s). The code could quickly become brittle if you were to hard-code a whitelist of development/demo/qa/prod domains, or weak if you hard-code a blacklist. Also, it's fairly easy to intentionally break a frame buster. In the end, if you get into an arms race with a framer, you will lose.

Work around #2: x-frame-options

Newer browsers actually have a mechanism to dis-allow framing your site all together, via the x-frame-options HTTP header. This is great, but only works on newish browsers, and is somewhat coarsely grained. You can only make an exception for same domain IFRAMEs, not arbitrary domains. Also, the user experience kind of blows; it looks like an error to the end-user.

Work around #3: P3P

You can send a HTTP header to tell the browser to allow third-party cookies in this instance. This uses the P3P standard. However, it should be noted that you're essentially making legally binding claims about how you handle user data. For example, the smallest change you can make that will notify IE to allow third-party by default is:

vim /etc/apache2/sites-available/default

# add the following
Header append P3P "CP=\"CAO PSA OUR\""

ln -s /etc/apache2/mods-available/headers.load /etc/apache2/mods-enabled/headers.load
service apache2 restart

Here is a quick rundown of what legal claims you're making in this case.

  • CAO - Identified Contact Information and Other Identified Data: access is given to identified online and physical contact information as well as to certain other identified data.
  • PSA - Information may be used to create or build a record of a particular individual or computer that is tied to a pseudonymous identifier, without tying identified data.
  • OUR - Ourselves and/or entities acting as our agents or entities for whom we are acting as an agent (can access the data).

Work around #4: Custom CSRF error page

This isn't really a work-around, and should probably be done anyway. But Django gives you the ability to define a custom error page for CSRF validation errors, where could could tell the user what the problem is, and maybe have them to a manual frame break.