3

I'm using tokens to prevent CSRF attacks in my application. But this application is single page and heavily AJAX based, so I need to find a way to provide a valid token for N actions in a single page:

e.g. A document fragment loads with 3 possible actions, each action needs a token to work properly but server side has just one valid token at time...

Since each token is just a encrypted nonce (the value isn't based in a specific form), I came with the idea of automatize the token assignation for each action with something like this:

  1. The App intercepts an AJAX call, and check if it's a sensitive action (i.e. delete a user)
  2. A token is requested to the server before the action proceed
  3. The token is generated and then added to the action request
  4. The action in executed since the request included a valid token
  5. Do the same for any subsequent actions executed via AJAX

I believe that method isn't effective enough because the attacker can use a script that does exactly the same my App does (retrieve token and append to the request).

How can I improve my method to be effective against CSRF attacks?

Additional info: My backend environment is PHP/Phalcon and the tokens are generated using Phalcon.

cvsguimaraes
  • 12,910
  • 9
  • 49
  • 73
  • 1
    How would an attacker login and do the request, assuming you login with sessions and dont just allow non logged in users to delete users? If its a true CSRF attack on you to delete the user how would the attacker know your nonce? If its a public API yes they can scrape your page first get the nonce and then do the request, you cant stop that 100%, there are layers you can add but still. You could even go as far as instead of a nonce use an encrypted string with parameters related to the user, but generally its overkill. – Lawrence Cherone May 03 '14 at 18:19
  • _“each action needs a token to work properly but server side has just one valid token at time...”_ – well then change that: Generate a different token for _each_ possible action. – CBroe May 03 '14 at 18:36

2 Answers2

3

A simpler method than using tokens for an AJAX only might be to check headers that can only be present in an AJAX request from your own domain.

Two options are:

The Origin header can also be used for normal HTML form submissions, and you would verify that this contains the URL of your own site before the form submission does its processing.

If you decide to check the X-Requested-With header, you will need to make sure it is added to each AJAX request client side (JQuery will do this by default). As this header cannot be sent cross domain (without your server's consent to the browser first), checking that this is present and set to your own value (e.g. "XMLHttpRequest") will verify that the request is not a CSRF attack.

Community
  • 1
  • 1
SilverlightFox
  • 32,436
  • 11
  • 76
  • 145
  • @IanBytchek This whole wiki is full of must-know information – cvsguimaraes May 05 '14 at 21:51
  • Only left to decide what really is a must must-know :P – Ian Bytchek May 05 '14 at 22:39
  • @SilverlightFox, skimmed through, earlier browsers don't support CORS. So, those technics are apply mostly only AJAX requests, right? I assume this can be bypassed using curl if I compile a valid request? – Ian Bytchek May 05 '14 at 22:44
  • I think I get. Good read – http://stackoverflow.com/questions/4850702/is-cors-a-secure-way-to-do-cross-domain-ajax-requests – Ian Bytchek May 05 '14 at 23:00
  • @IanBytchek Yes, the `Origin` header will only be present in CORS supported browsers, but the restriction on `X-Requested-With` _should_ apply to pre-CORS cross domain requests too. Warning: Results may vary between browsers. Yes, they can be spoofed by `curl` but in this case the attacker will not have the victim's cookies injected by the browser (I suspect you already know this now). – SilverlightFox May 06 '14 at 09:37
2

I had to deal with something similar awhile ago. Requesting nonces with ajax is a super bad idea – IMHO, it invalidates the whole point of having them if the attacker can simply generate it without reloading the page. I ended up implementing the following:

  1. Nonce module (the brain of the operation) that handles creation, destruction, validation and hierarchy of nonces (e.g., child nonces for one page with multiple inputs).
  2. Whenever a form / certain input is rendered, nonce is generated and stored in a session with expire timestamp.
  3. When the user is done with an action / form / page, the nonce with it's hierarchy is destroyed. Request may return a new nonce if the action is repetitive.
  4. Upon generating a new nonce old ones are checked and expired ones are removed.

The major trouble with it is deciding when the nonce expires and cleaning them up, because they grow like bacteria on steroids. You don't want a user to submit a form that was open for an hour and get stuck because the nonce is expired / deleted. In those situations you can return 'time out, please try again' message with the regenerated nonce, so upon the following request everything would pass.

As already suggested, nothing is 100% bullet proof and in most cases is an overkill. I think this approach is a good balance between being paranoid and not wasting days of time on it. Did it help me a lot? It did more with being paranoid about it compared to improving the security dramatically.

Logically thinking, the best thing you could do in those situations is to analyse the behaviour behind the requests and time them out if they get suspicious. For example, 20 requests per minute from one ip, track mouse / keyboard, make sure they are active between the requests. In other words ensure that requests are not automated, instead of ensuring they come with valid nonces.

Ian Bytchek
  • 8,804
  • 6
  • 46
  • 72
  • Have you considered XSS attacks in your solution? – cvsguimaraes May 05 '14 at 21:10
  • The mission was to prevent unwanted data being freely submitted to the server. This is a very generic solution, it's not 100% bullet proof, but it does stop the majority of automated form submissions. XSS is a very different story and I don't see how using tokens can help to solve it at all :) It can only be prevented by using common sense, not working late nights, and testing your code for loopholes. – Ian Bytchek May 05 '14 at 21:32