Cross-Site Request Forgeries (CSRF) exploit the trust that a site has within a user’s browser. By inducing clicks on links to sites where users are suspected to be authenticated, perpetrators can execute transactions under the umbrella of a user’s current session. Requiring newly generated parameter values with each new POST or GET is one way for programmers to protect against CSRF. But while implementing this requirement in page-driven applications is fairly straight-forward, ajaxified apps make things more complicated. The following approach lets us abstract the complications out of our day-to-day so we can code both currently and securely.
Instead of
bank.com/withdraw?account=bob&amount=1000000&for=mallory
we’re now going to require
bank.com/withdraw?account=bob&amount=1000000&for=mallory&csrfProtect=1234567890
where the csrfProtect value is updated with each server hit. As new pages are constructed on the application server, the csrfProtect parameter and latest value are appended to any static links or actions that kick off GETs or POSTs. Thus when legit users fire the requests from the pages returned to their browsers, they also supply a current csrfProtect value that signals all is aboveboard.
The complication in these being fired through ajax is twofold. First, for each request we need to dynamically detect and include a current csrfProtect value. Then on the response, because the app server isn’t given the chance to construct a new page, we need to iterate through in-page links and actions and update their parameter value.
The easiest way I’ve found to keep up with the changing csrfProtect value is to simply require it be supplied in any page that might instigate ajax requests, and as part of the data in any ajax response (in the code below, the value is transported with input[type=hidden].csrfProtect). With these hooks and a standardization of IE’s implementation in place, we can modify the XMLHttpRequest object’s prototype with a new csrfProtect property and a custom open method that intercepts and updates requests before they are sent to the server. jQuery’s ajaxComplete method keeps XMLHttpRequest.prototype.csrfProtect up-to-date and ready for the next request.
1: /*
2: include jQuery
3: include http://code.google.com/p/xmlhttprequest to standardize XMLHttpRequest as constructor function in IE<7 (info at http://www.ilinsky.com/articles/XMLHttpRequest/)
4: */
5:
6: //override jQuery's default IE<7 call for the ActiveX object with standard constructor (available to IE<7 via ilinsky.com script)
7: $.ajaxSetup({
8: xhr:function(){return new XMLHttpRequest();}
9: });
10:
11: (function(){
12: var $links,//in-page elements whose csrfProtect param value needs updating on ajax responses
13: b;//'?' or '&' depending on if requested URL already has params
14:
15: //Store off native XMLHttpRequest.open as XMLHttpRequest.nativeOpen
16: XMLHttpRequest.prototype.nativeOpen = (function(){
17: return XMLHttpRequest.prototype.open;
18: }());
19:
20: //Redefine XMLHttpRequest.open to intercept and modify request
21: XMLHttpRequest.prototype.open = function(){
22:
23: var a=arguments;//originally supplied ajax arguments
24:
25: //Mutate first instance (initial request) of XMLHttpRequest with csrfProtect property and value if 'csrfProtect' class is found within the page
26: if(!this.csrfProtect&&$('.csrfProtect').length>0){
27: this.csrfProtect=$('.csrfProtect:eq(0)').val();
28: }
29:
30: if(this.csrfProtect){
31: a[1]=a[1]+(function(){a[1].indexOf('?')>0?b='&':b='?';return b}())+'csrfProtect='+this.csrfProtect;//append csrfProtect parameter and value to requested url
32: }
33: this.nativeOpen(a[0],a[1],a[2],a[3],a[4]);//open request
34: };
35:
36: $(window).ajaxComplete(function(event,xhr,settings){
37: var csrfSplit=xhr.responseText.split('class="csrfProtect" VALUE='),
38: csrfValue;
39: if(csrfSplit.length>1){
40: csrfValue=csrfSplit[1].split('>')[0];
41: this.XMLHttpRequest.prototype.csrfProtect=csrfValue;
42: }
43: //update page links with latest csrfProtect value
44: $links.each(function(){
45: if(this.href){this.href=this.href.replace(/csrfProtect.*/,'csrfProtect='+this.XMLHttpRequest.prototype.csrfProtect);}//this replace makes the assumption that csrfProtect param and value are always last in URL
46: });
47:
48: });
49:
50: //links
51: $(document).ready(function(){
52: $links=$('a');//modify this selector and $(window).ajaxComplete logic per elements that need updating
53: });
54: }());
The sleeper in this implementation is Sergey Ilinsky’s script, which not only exposes the XMLHttpRequest’s prototype property in IE but standardizes the ajax implementation across all browsers. In the code above we take advantage of the jQuery wrapper and methods because we’re working in a jQuery shop. XMLHttpRequest.js, however, gives us the same cross-browser access to ajax states that jQuery does. If we weren’t already incurring the jQuery overhead, we could achieve the same results with some slightly rawer JavaScript.