CakePHP中的表单键(CSRF)


Form keys (CSRF) in CakePHP

我在一些MVC应用程序中看到使用令牌密钥来防止CSRF。它可以在哪里使用的一个典型例子是关于帖子的删除方法。

我看到了同时使用GET和POST方法的实现。

带有令牌的GET请求链接示例:

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

还有一个带有令牌的POST请求示例:

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

我一直在考虑基于以下文档将其实现到我的CakePHP应用程序中:http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html

根据文档,添加Security组件会自动将表单密钥添加到所有使用form Helper的表单中。

例如

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

但是我有一些问题:

1.)为什么要对一些操作(如删除)使用POST而不是GET?由于控制器中的请求将检查用户是否经过身份验证、是否具有权限以及是否具有正确的表单密钥。

2.)如何在CakePHP中使用GET请求的安全组件?我想我也需要处理路由。

首先,CakePHP使用post链接进行删除,作为一种额外的安全级别,因为例如,您的身份验证系统不是100%安全的,用户可以通过手动键入URL来访问删除方法-如果我进入并键入/users/delete/10,我实际上可以删除您服务器上的用户,这正如您所想象的那样是有风险的。

其次,GET请求可以被缓存或添加书签,因此为这些链接添加书签的用户最终可能会导航到断开的链接,这从来都不是一件好事,而且敏感数据也会在URL中可见,例如,如果有人在登录页面上添加了完整的GET变量书签,这可能会危及安全性。

最后,您可以使用以下代码轻松生成自己的代币:

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

以上内容将使用核心配置文件中的salt键设置,但您也可以用自定义salt替换true。

对于第一个问题:

原因可能在于HTTP方法的定义。GET被定义为安全方法之一,这意味着它不能用于更改服务器的状态,而只能用于检索信息。您可以在此链接上阅读有关HTTP方法的更多信息。由于HTML表单无法发送HTTP DELETE请求,因此"解决方法"是使用一些可用的方法,如果您排除了GET作为"安全方法"的可能性,则会留下POST。当然,您可以使用GET来删除内容,许多人都这样做,但按照惯例,GET请求不会更改任何内容。

编辑:如果您有兴趣阅读更多关于HTTP方法和浏览器/HTML支持的信息,请查看此SO问题

johhniedoe向我介绍了Croogo 1.3,以了解它们是如何做的。因为1.3是针对2.x之前的CakePHP的,所以代码有点过时,所以我对它进行了如下修改:

首先创建链接(在本例中为删除链接),其中安全组件使用的CSRF令牌作为一个名为token的参数传递。

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

接下来,我创建了一个通配符路由连接来处理令牌(这通常不是必要的,但因为我们没有使用NAMED令牌,我想保持URL更干净):

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

然后最后用你的方法这样处理:

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

这基本上是说,如果令牌不匹配,那么使用黑洞回调函数__blackhole,我在AppController中定义它来显示错误。

不过,最后需要注意的是,您必须允许令牌使用一次以上,例如持续一个小时,这是因为否则,在发送GET请求后,令牌将被重置,不再匹配。

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