The method of preventing CSRF attacks described in this post is now considered to be insufficient. A comment on this post links to more details about an attack that circumvents it.
About two weeks ago, I discovered a CSRF vulnerability on a well-known website (I won’t mention it by name). The vulnerability itself was fairly mundane: one of their scripts lacked tokens or any other form of CSRF protection. I sent in an email to their security team to let them know about it and went on my way.
The other day, I looked back at the website to see if the vulnerability had been fixed: it had. I also discovered that the vulnerability had been fixed using a method I had never thought about before. The website used “standard” HTTP headers, rather than a random token in the query string, to ensure that the request was from a valid source.
How does it work?
On the server side, the server validates the request to make sure the header exists in the request. If it doesn’t, the request is rejected. If you’re using a library like jQuery, this is the only bit of code you have to implement.
Why does it work?
To add the header to a request within the context of the browser (which is what you need to do to pull off a CSRF attack properly), the attacker needs to use XMLHttpRequest. However, thanks to same-origin restrictions, XMLHttpRequest doesn’t allow you to make an AJAX request to a third party domain by default. Thus, it’s not possible for an attacker to forge a request with a spoofed X-Requested-With header.
There are, of course, some caveats:
- You need to use AJAX requests to take advantage of this protection: regular GETs and POSTs don’t allow for header manipulation.
- If your site allows some form of cross-domain AJAX requests (see https://developer.mozilla.org/en/http_access_control for more details), this won’t protect you.
- Like any other form of CSRF protection, it can be circumvented under certain circumstances. An insecure crossdomain.xml file, for instance, will still allow malicious Flash applets to go wild on your website.
It is, however, much better than using the Referer header.