Protect your website against CSRF attacks
CSRF attacks take advantage of the fact that if an authenticated client opens a page with a link
, the browser will treat it as a regular link (normal!) and send over the credentials to the website, thus allowing the action to be performed.
This component's goal is to suppress that risk by protecting your links with a secret.
This component's goal is to suppress that risk by protecting your links with a secret.
Everytime an action is authenticated and performed, the component will automatically regenerate a new name and new value for the secret parameter in URL.
Once this component is installed, all you have to do to protect your website is to add the SecureAction component in your controller and feed the property $securedActions with the actions you want to protect.
You will still have of course (I don't know how to that nicely in CakePHP) to generate the links of the actions to be protected by using the component method $this->SecureAction->url($originalURL); in your controllers or $html->surl($originalURL); in your views:
Now the component code you will need to save in app/controllers/components/secure_action.php:
And finally you need to add in your app_helper.php (for simplicity, to allow access from any Helper, like HtmlHelper):
Here you go, hope this component will be usefull :)
Thanks for http://bakery.cakephp.org/articles/view/secureget-component to give me some usefull code to start working on right away.
And thanks to the users of http://www.lescigales.org/ to let me know about the issue ;)
Once this component is installed, all you have to do to protect your website is to add the SecureAction component in your controller and feed the property $securedActions with the actions you want to protect.
Controller Class:
<?php
class UsersController extends AppController
{
var $components = array('SecureAction');
// var $securedActions = '*'; // protect all actions of the controller
var $securedActions = array('remove');
function remove($userID)
{
$this->User->delete($userID);
}
}
?>
You will still have of course (I don't know how to that nicely in CakePHP) to generate the links of the actions to be protected by using the component method $this->SecureAction->url($originalURL); in your controllers or $html->surl($originalURL); in your views:
Controller Class:
<?php
...
function anything()
{
$url = $this->SecureAction->url('/users/delete/54');
$this->set('deleteLink', $url);
}
...
?>
View Template:
...
<p>Delete this user by clicking on the following link:
<?php echo $html->link('Delete', $html->surl('/users/delete/54')); ?>
</p>
...
Now the component code you will need to save in app/controllers/components/secure_action.php:
Component Class:
<?php
<?php
/** Sexy rip of: http://bakery.cakephp.org/articles/view/secureget-component */
class SecureActionComponent extends Object
{
var $name = 'SecureAction';
var $components = array('Session', 'Flash');
var $idLength = 16;
var $nameLength = 4;
function startup(&$controller)
{
if (! $this->Session->check($this->name.'.name') || ! $this->Session->check($this->name.'.hashKey')) {
$this->regenerate();
}
/** Authenticate this action if necessary */
$this->__action = strtolower($controller->action);
if (! empty($controller->securedActions)) {
if ($controller->securedActions == '*' || in_array($this->__action, $controller->securedActions)) {
/** Auth required */
$rv = $this->auth($controller->params['url']['url']);
if ($rv == false) {
/** Sets a flash message and redirect */
$controller->Flash->add(__("You do not have right to perform the previous action", true));
if (env('HTTP_REFERER') == '') {
$controller->redirect('/');
}
$controller->redirect(env('HTTP_REFERER'));
} else {
/** Access granted, lets regenerate the key */
$this->regenerate();
}
}
}
}
function regenerate()
{
$this->Session->write($this->name.'.name', $this->_generate($this->nameLength));
$this->Session->write($this->name.'.hashKey', $this->_generate());
}
/**
* Authenticate the given action
* @returns false on error, true on success
*/
function auth($url)
{
if (empty($url)) {
return false;
}
if ($url[0] != '/') {
$url = '/'.$url;
}
$url_t = explode('/', $url);
$key = null;
for ($i = 0; isset($url_t[$i]); $i++) {
if (! strncmp($url_t[$i], $this->Session->read($this->name.'.name').':', $this->nameLength+1)) {
$key = $url_t[$i];
}
}
if ($key == null) {
return false;
}
$url = str_replace($key, '', $url); // we remove the key from the URI
$lid = str_replace('/', '', $url); // we remove all slashes
$key_t = explode(':', $key); // we isolate the key from its name
$nkey = sha1($this->Session->read($this->name.'.hashKey').$lid);
if ($nkey == $key_t[1]) {
return true;
}
return false;
}
/**
* Generate an url from the full url (/controller/action/param1:value1/etc...)
*/
function url($url)
{
$lid = str_replace('/', '', $url);
// $lid = explode('/', $url);
// $lid = implode('', $lid);
$key = sha1($this->Session->read($this->name.'.hashKey').$lid);
$url .= '/'.$this->Session->read($this->name.'.name').':'.$key;
return $url;
}
function _generate($length = null)
{
if (! is_n($length)) {
$length = $this->idLength;
}
$chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$max = strlen($chars)-1;
$string = '';
for ($i = 0; $i < $length; $i++) {
$string .= $chars[mt_rand(0, $max)];
}
return $string;
}
}
?>
And finally you need to add in your app_helper.php (for simplicity, to allow access from any Helper, like HtmlHelper):
Helper Class:
<?php
class AppHelper extends Helper {
/** Check SecureAction component */
function surl($url) {
$view =& ClassRegistry::getObject('view');
$lid = str_replace('/', '', $url);
$key = sha1($view->loaded['session']->read('SecureAction.hashKey').$lid);
$url .= '/'.$view->loaded['session']->read('SecureAction.name').':'.$key;
return $url;
}
}
?>
Here you go, hope this component will be usefull :)
Thanks for http://bakery.cakephp.org/articles/view/secureget-component to give me some usefull code to start working on right away.
And thanks to the users of http://www.lescigales.org/ to let me know about the issue ;)








First, make a small change in your view where you create the link so that the named parameters of the current page are included in the secure link:
echo $this->HtmlExt->slink(__('Delete', true), array_merge(array('action'=>'delete', $id), $this->passedArgs) , null, __('Are you sure you want to delete this record?', true)); ?>
Then in your controller, change the redirect in the secured action to include named parameters:
$this->redirect(array_merge(array('action'=>'index'), $this->params['named']));
Now, here's the change needed for the SecureAction component. Add the following function to the SecureActionComponent class:
function removeParam(&$controller) {
$key = $this->Session->read($this->name.'.name');
if (isset($controller->params['named'][$key])) {
unset($controller->params['named'][$key]);
}
}
Then, add a call to this new function in the startup method just after the authentication check:
$rv = $this->auth($controller->params['url']['url']);
$this->removeParam($controller);
if ($rv == false) {
Line 5:
var $components = array('Session', 'Flash');Replace with:var $components = array('Session');Line 25:
$controller->Flash->add(__("You do not have right to perform the previous action", true));Replace with:$this->Session->setFlash(__("You do not have right to perform the previous action", true));Line 94:
if (! is_n($length)) {Replace with:if (empty($length)) {If you're using CakePHP's HTML helper to create links, the surl function's output can't be directly passed to $html->link. It took a while to sort out this problem. The component expects the token to be created using part of the URL below Cake's webroot. It turns out that using $html->surl and $html->link together with $html->url results in the webroot part of the URL being included in the address, or included more than once in the URL. To get around this, instead of placing the surl function in AppHelper, I created a new HtmlExt helper:
html_ext.php
Helper Class:
<?php
class HtmlExtHelper extends HtmlHelper {
/**
* SecureAction's surl method. Add secure token to a URL.
* @param string $url Cake-relative URL
* @return string URL with token added
*/
function surl($url) {
$view =& ClassRegistry::getObject('view');
$lid = str_replace('/', '', $url);
$key = sha1($view->loaded['session']->read('SecureAction.hashKey').$lid);
$url .= '/'.$view->loaded['session']->read('SecureAction.name').':'.$key;
return $url;
}
/**
* Cake's HtmlHelper link method, modified so that that the URL is given a SecureAction token
*/
function slink($title, $url = null, $htmlAttributes = array(), $confirmMessage = false, $escapeTitle = true) {
if ($url !== null) {
$url = $this->url($url);
} else {
$url = $this->url($title);
$title = $url;
$escapeTitle = false;
}
// This is the part added for secure url's
$turl = substr($url, strlen($this->webroot));
$url = $this->webroot . $this->surl($turl);
if (isset($htmlAttributes['escape']) && $escapeTitle == true) {
$escapeTitle = $htmlAttributes['escape'];
}
if ($escapeTitle === true) {
$title = h($title);
} elseif (is_string($escapeTitle)) {
$title = htmlentities($title, ENT_QUOTES, $escapeTitle);
}
if (!empty($htmlAttributes['confirm'])) {
$confirmMessage = $htmlAttributes['confirm'];
unset($htmlAttributes['confirm']);
}
if ($confirmMessage) {
$confirmMessage = str_replace("'", "\'", $confirmMessage);
$confirmMessage = str_replace('"', '\"', $confirmMessage);
$htmlAttributes['onclick'] = "return confirm('{$confirmMessage}');";
} elseif (isset($htmlAttributes['default']) && $htmlAttributes['default'] == false) {
if (isset($htmlAttributes['onclick'])) {
$htmlAttributes['onclick'] .= ' event.returnValue = false; return false;';
} else {
$htmlAttributes['onclick'] = 'event.returnValue = false; return false;';
}
unset($htmlAttributes['default']);
}
return $this->output(sprintf($this->tags['link'], $url, $this->_parseAttributes($htmlAttributes), $title));
}
}
?>
Usage works almost exactly like the normal $html->link method. For example:
echo $htmlExt->slink(__('Delete', true), array('controller'=>'users', 'action'=>'delete', $user['User']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $user['User']['id']));I'd like to see this included in CakePHP's core Security component. Ideally, actions specified in the $securedActions array would be automatically appended with a secure token. I don't know if this is possible though, it would probably require some modifications to Router::url.
I totally agree with that, but the reality is quite different, hence this component. More and more people are using GET requests to manipulate data as a shortcut (I cannot blame neither them nor me, since it's the power the tools of the web give to us).
Standards are good, but sticking to reality in this world is even better !
Comments are closed for articles over a year old