CSRF Protection via X-Browser jQuery Ajax Hijack

October 28th, 2010 § 0

Cross-Site Re­quest For­ger­ies (CSRF) ex­ploit the trust that a site has with­in a user’s browser. By in­du­cing clicks on links to sites where users are sus­pec­ted to be au­then­tic­ated, per­pet­rat­ors can ex­ecute trans­ac­tions un­der the um­brella of a user’s cur­rent ses­sion. Re­quir­ing newly gen­er­ated para­met­er val­ues with each new POST or GET is one way for pro­gram­mers to pro­tect against CSRF. But while im­ple­ment­ing this re­quire­ment in page-driv­en ap­plic­a­tions is fairly straight-for­ward, ajaxi­fied apps make things more com­plic­ated. The fol­low­ing ap­proach lets us ab­stract the com­plic­a­tions out of our day-to-day so we can code both cur­rently and se­curely.

In­stead of

bank.com/with­draw?ac­count=bob&amount=1000000&for=mal­lory

we’re now go­ing to re­quire

bank.com/with­draw?ac­count=bob&amount=1000000&for=mal­lory&csr­f­Pro­tect=1234567890

where the csr­f­Pro­tect value is up­dated with each serv­er hit. As new pages are con­struc­ted on the ap­plic­a­tion serv­er, the csr­f­Pro­tect para­met­er and latest value are ap­pen­ded to any stat­ic links or ac­tions that kick off GETs or POSTs. Thus when le­git users fire the re­quests from the pages re­turned to their browsers, they also sup­ply a cur­rent csr­f­Pro­tect value that sig­nals all is above­board.

The com­plic­a­tion in these be­ing fired through ajax is two­fold. First, for each re­quest we need to dy­nam­ic­ally de­tect and in­clude a cur­rent csr­f­Pro­tect value. Then on the re­sponse, be­cause the app serv­er isn’t giv­en the chance to con­struct a new page, we need to it­er­ate through in-page links and ac­tions and up­date their para­met­er value.

The easi­est way I’ve found to keep up with the chan­ging csr­f­Pro­tect value is to simply re­quire it be sup­plied in any page that might in­stig­ate ajax re­quests, and as part of the data in any ajax re­sponse (in the code be­low, the value is trans­por­ted with in­put[type=hid­den].csr­f­Pro­tect). With these hooks and a stand­ard­iz­a­tion of IE’s im­ple­ment­a­tion in place, we can modi­fy the XM­L­Ht­tpRe­quest ob­ject’s pro­to­type with a new csr­f­Pro­tect prop­erty and a cus­tom open meth­od that in­ter­cepts and up­dates re­quests be­fore they are sent to the serv­er. jQuery’s ajax­Com­plete meth­od keeps XM­L­Ht­tpRe­quest.pro­to­type.csr­f­Pro­tect up-to-date and ready for the next re­quest.

  1: /*

  2: in­clude jQuery

  3: in­clude ht­tp://code.google.com/p/xm­l­ht­tpre­quest to stand­ard­ize XM­L­Ht­tpRe­quest as con­struct­or func­tion in IE<7 (info at ht­tp://www.il­in­sky.com/art­icles/XM­L­Ht­tpRe­quest/)

  4: */

  5:

  6: //over­ride jQuery's de­fault IE<7 call for the Act­iveX ob­ject with stand­ard con­struct­or (avail­able to IE<7 via il­in­sky.com script)

  7: $.ajax­Setup({

  8:   xhr:func­tion(){re­turn new XM­L­Ht­tpRe­quest();}

  9: });

 10:

 11: (func­tion(){

 12:   var $links,//in-page ele­ments whose csr­f­Pro­tect param value needs up­dat­ing on ajax re­sponses 

 13:     b;//'?' or '&' de­pend­ing on if re­ques­ted URL already has params

 14:

 15:   //Store off nat­ive XM­L­Ht­tpRe­quest.open as XM­L­Ht­tpRe­quest.nat­i­ve­Open

 16:   XM­L­Ht­tpRe­quest.pro­to­type.nat­i­ve­Open = (func­tion(){

 17:     re­turn XM­L­Ht­tpRe­quest.pro­to­type.open;

 18:   }());

 19:

 20:   //Re­define XM­L­Ht­tpRe­quest.open to in­ter­cept and modi­fy re­quest

 21:   XM­L­Ht­tpRe­quest.pro­to­type.open = func­tion(){

 22:

 23:     var a=ar­gu­ments;//ori­gin­ally sup­plied ajax ar­gu­ments

 24:

 25:     //Mutate first in­stance (ini­tial re­quest) of XM­L­Ht­tpRe­quest with csr­f­Pro­tect prop­erty and value if 'csr­f­Pro­tect' class is found with­in the page

 26:     if(!this.csr­f­Pro­tect&&$('.csr­f­Pro­tect').length>0){

 27:       this.csr­f­Pro­tect=$('.csr­f­Pro­tect:eq(0)').val();

 28:     }

 29:

 30:     if(this.csr­f­Pro­tect){

 31:       a[1]=a[1]+(func­tion(){a[1].in­dex­Of('?')>0?b='&':b='?';re­turn b}())+'csr­f­Pro­tect='+this.csr­f­Pro­tect;//ap­pend csr­f­Pro­tect para­met­er and value to re­ques­ted url

 32:     }

 33:     this.nat­i­ve­Open(a[0],a[1],a[2],a[3],a[4]);//open re­quest

 34:   };

 35:

 36:   $(win­dow).ajax­Com­plete(func­tion(event,xhr,set­tings){

 37:     var csrf­Split=xhr.re­spon­se­Text.split('class="csr­f­Pro­tect" VALUE='),

 38:       csr­f­Value;

 39:     if(csrf­Split.length>1){

 40:       csr­f­Value=csrf­Split[1].split('>')[0];

 41:       this.XM­L­Ht­tpRe­quest.pro­to­type.csr­f­Pro­tect=csr­f­Value;

 42:     }

 43:     //up­date page links with latest csr­f­Pro­tect value

 44:     $links.each(func­tion(){

 45:       if(this.href){this.href=this.href.re­place(/csr­f­Pro­tect.*/,'csr­f­Pro­tect='+this.XM­L­Ht­tpRe­quest.pro­to­type.csr­f­Pro­tect);}//this re­place makes the as­sump­tion that csr­f­Pro­tect param and value are al­ways last in URL

 46:     });

 47:

 48:   });

 49:

 50:   //links

 51:   $(doc­u­ment).ready(func­tion(){

 52:     $links=$('a');//modi­fy this se­lect­or and $(win­dow).ajax­Com­plete lo­gic per ele­ments that need up­dat­ing

 53:   });

 54: }());

The sleep­er in this im­ple­ment­a­tion is Sergey Il­in­sky’s script, which not only ex­poses the XM­L­Ht­tpRe­quest’s pro­to­type prop­erty in IE but stand­ard­izes the ajax im­ple­ment­a­tion across all browsers. In the code above we take ad­vant­age of the jQuery wrap­per and meth­ods be­cause we’re work­ing in a jQuery shop. XM­L­Ht­tpRe­quest.js, however, gives us the same cross-browser ac­cess to ajax states that jQuery does. If we wer­en’t already in­cur­ring the jQuery over­head, we could achieve the same res­ults with some slightly raw­er JavaS­cript.

§ Leave a Comment

meta

Tags: , , ,