1

I have seen in some MVC applications the use of Token keys to prevent CSRF. A typical example of where it may be used is on the delete method for a post.

And I've seen implementations using both GET and POST methods.

An example GET request link with a token:

https://domain.com/posts/G7j/delete/EOwFwC4TIIydMVUHMXZZdkbUR0cluRSkFzecQy3m5pMTYVXRkcFIBWUZYLNUNSNgQKdnpTWu

And an example of a POST request with a token:

<form action="/posts/G7j/delete" method="post">
    <input type="hidden" name="token" value="EOwFwC4TIIydMVUHMXZZdkbUR0cluRSkFzecQy3m5pMTYVXRkcFIBWUZYLNUNSNgQKdnpTWu" />
    <button type="submit">Delete</button>
</form>

I've been looking into implementing this into my CakePHP applications based on the documents: http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html

And according to the documents, adding the Security component auto adds the form key to all forms that use the Form Helper.

e.g.

public $components = array(
    'Security' => array(
        'csrfExpires' => '+1 hour'
    )
);

However I have some questions:

1.) Why use POST over GET for some actions such as delete? As the request in the controller will check if the user is authenticated, has permission, and that it has the correct form key.

2.) How can I use the security component with a GET request in CakePHP? Presume I would also need to handle the routing as well.

Cameron
  • 27,963
  • 100
  • 281
  • 483
  • "And according to the documents, adding the Security component auto adds the form key to all forms that use the Form Helper." - if you insist on GET you can always make this form use get method instead of post, but i guess it's not what you actually want. As solution itself, im not writing an answer as I do not have solution, what I do have is tip, that in Croogo CMS, which is a CMS on cakePHP has this feature implemented using Security's component csrf. Maybe you should look at their code to get a hint how they have done it. – johhniedoe Aug 22 '13 at 13:41
  • Where in Croogo have you seen this? Thanks – Cameron Aug 22 '13 at 16:01
  • Version 1.3: - in view ( ex. for nodes ), find delete link - in controller for nodes find admin_delete() method If not mistaken, they do use Token from Security component. – johhniedoe Aug 23 '13 at 08:38
  • Cool :) That's quite a nice way to handle it. Thanks. – Cameron Aug 23 '13 at 12:27

3 Answers3

3

Firstly CakePHP uses post links to delete as an added level of security because lets say for example your authentication system is not 100% secure and users can access a delete method by manually typing in a URL - if I go in and type /users/delete/10 , I can actually delete users on your server which is risky as you can imagine.

Secondarily GET requests can be cached or bookmarked so users who bookmark these links could end up navigating to broken links which is never a good thing ,also sensitive data will be visible in the URL so for example if someone bookmarks a login page with the GET variables intact - this could compromise there security.

Finally you can easily generate your own tokens using the following code :

$string = Security::hash('string', 'sha1 or md5', true);
print $this->Html->link("Link to somewhere",array("controller"=>"users","action"=>"delete",$string));

The above will use the salt key setup in your core config file but you can also replace true with a custom salt.

KevinCoder
  • 191
  • 5
  • And how would I then make use of that token on the controller end to check it's valid? Without writing a whole load of mess to check in the method itself? – Cameron Aug 22 '13 at 09:35
  • +1 for realizing a user is going to bookmark a broken GET page. That's something really important as CSRF protection in general is going to break your bookmarks, so you just need to be aware of this and fail gracefully if an invalid token is passed. – DaOgre Aug 23 '13 at 00:20
  • Thanks, @Cameron - When generating the hash above you can store in a session: $this->Session->write("token",$string) and the above code will pass on the $string variable to your action method , simply catch this in a your action method and compare to the one you wrote to the session. – KevinCoder Aug 23 '13 at 10:25
2

For the first question:

The reason probably lies in the definition of HTTP methods. GET is defined as one of the safe methods, which means that it can not be used for changing the state of the server but only for retrieving information. You can read more on the HTTP methods on this link. Since HTML forms are not capable of sending HTTP DELETE request the 'workaround' is to use some of the available methods and if you rule out the GET as a 'safe method' it leaves POST. You can of course use GET to delete stuff, many do, but GET request is by convention expected not to change anything.

edit: If you are interested to read more about HTTP methods and browser/HTML support check out this SO question

Community
  • 1
  • 1
pajaja
  • 2,164
  • 4
  • 25
  • 33
1

johhniedoe pointed me to Croogo 1.3 for an example of how they are have done something similar to what I asked in my question. Because 1.3 was targeted to CakePHP prior to 2.x, the code was a little out of date, so I've amended it as follows:

First create the link (in this case a delete link) with the CSRF Token used by the Security Component passed as a parameter called token.

<?php echo $this->Html->link('Delete', array('action'=>'delete','token'=>$this->params['_Token']['key'])); ?>

Next I've created a wildcard Route Connection to handle the token (this isn't essential usually, but because we're not using the NAMED Token and I wanted to keep the URL cleaner):

Router::connect('/:controller/:action/:token', 
        array('controller' => 'action'),
        array(
            'pass' => array('token')
        )
    );

And then finally handle it in your method like so:

public function delete(){

    if (!isset($this->params['token']) || ($this->params['token'] != $this->params['_Token']['key'])) {
        $this->Security->blackHoleCallback = '__blackhole';
    } else {
        // do delete
    }
}

This basically says if the token doesn't match then use the blackhole callback function __blackhole, which I define in my AppController to show an error.

One last thing to be aware of though is that you must allow the token to be used more than once and last for example an hour, this is because otherwise the token will be reset and no longer match after the GET request is sent.

public $components = array(
    'Security' => array(
        'csrfExpires' => '+1 hour',
        'csrfUseOnce' => false
    )
);
Community
  • 1
  • 1
Cameron
  • 27,963
  • 100
  • 281
  • 483