AD7six.com

Generic capability-based security (CSRF prevention)

23 August, 2007

Show comments

Over time I have become more and more interested (concerned?) with the security aspects of web development, and lately I've been looking at defence tactics against Cross-site request forgery type attacks. To put CSRF into layman speak using a typical example, its an exploitation of sites where the only means of checking whether or not to do something, is to check if the request came from a logged in user. To put it more technically it's exploiting a site that has no capability-based security.

The use of form tokens, to ensure that a form request is genuine, is an example of capability-based security and that's exactly what Cake's security component does for you. However if a controller action requires only url parameters to execute unless steps have been taken this action will still succeed if the url is simply typed into the address bar of a browser. This means the code is not HTTP safe and susceptible to CSRF attacks. With only a few changes to your application code it is possible to eliminate this risk.

Background§

A long time ago, when I was young, eager and stupid, I was considering the problem "search engines are destroying my demo data while they crawl". I showed some naivety in the solution I found in the blog [how to prevent search engines from executing an action][Prevent Robots from accessing an action] action. I was aware that it was a rather weak 'solution', as Chris pointed out, a GET request should never do anything - only POST requests should affect data, so I was looking at a problem that should not have needed to be solved.

The delete example§

Consider the following code:

Look vaguely familiar? It should be apparent that if you use scaffolding, baked code or anything similar (such as the above) - you are at risk of accidentally deleting your own data. If you are you thinking "it's Ok, there's access control on the site"? Be aware that access control does not prevent the problem discussed here, AC checks that the user can access a resource, in the case this resource does something it doesn't check if the user intended that something to take place. An example to clarify, consider this (hypothetical!) scenario:

  • I know that your site has an articles controller and a method named admin_delete which expects the id of the article to delete. I know that there is no logic in place other than "can User access Url"
  • I know you visit this site, I don't like the contents of your article 22 so include somewhere on this page
  • You login to your site and do whatever you wanted to do
  • You find your way to this blog and start reading
  • What will happen when your browser looks for that 'image', who will be the user that requests deleting article 22?

That's right, I got YOU to delete the article. If your session on your own site doesn't time out and the code looks similar to the above you'll see a friendly "Article 22 deleted" flash message on whatever is the next page you view.

At this point I'll point out that changing GET requests to POST request for actions such as delete will prevent accidental deletions and very-basic CSRF attacks from occurring - but as you probably know it is relatively trivial job to achieve the same via a POST request so that in itself is not a defence against malicious intentions.

Delete is the most obvious example, but if a controller action only requires the parameters in the URL to execute, the law of lazy-development means that more than likely the code is written such that it executes if the parameters are present and correct.

So what to do?§

The short answer is to make use of the security component. What is needed is a means of forcing potentially destructive actions to be actioned only via post (using the security component's requirePost mechanism) and ensure the request is genuine (which the security component takes care of for you).

Compare the generic app controller from before with the following:

There are a few of my own best practices included which I'll point out first: the admin_delete method is defined only once in the app controller - as it is the same for all controllers and will be inherited by all; In beforeRender the variable $data is set to the contents of $this->data if it isn't already assigned.

So now a little explanation; All this code does is the following:

  • The beforeFilter tells the security component to run the action _confirmAction if it finds a problem. It also tells the security component to require POST requests for any method named in the controller variable $postActions
  • If the security component notices a problem, the method _confirmAction will be called.
    • For a GET request for an action that requires POST set some variables and render the view file APP/views/_generic/confirm_action.ctp
    • For any other error redirect to the home page with a message
  • The admin delete method can now only be reached via an (authenticated) POST request - part of the posted data is the original page referer to which the user is redirected after the method is processed

The generic (and rather simple) confirmation view looks like this:

Ta da! That's it.

If there are any other methods in the controller which require only URL parameters to execute (publish, make_favorite, rate, report_spam etc.), these can be added by modifying/overriding the controller $postActions parameter and they will then have a confirmation form before execution.

Wrapping Up§

Security should be everyone's concern. Presented here is a solution to add capability-based security to actions which can execute with only url parameters such that they cannot be executed either accidentally or maliciously and thus removing the risk of CSRF attack. If you use a similar technique, can think of an improvement to this technique, love it or even if you think it's awful please leave a comment.

Bake on!