我已经读过如何在最后几天防止CSRF攻击.我将在每个页面加载中更新令牌,将令牌保存在会话中并在提交表单时进行检查.
但是如果用户有,假设我的网站打开了3个标签,我只是将最后一个标记存储在会话中?这将使用另一个令牌覆盖令牌,并且一些后续操作将失败.
我是否需要在会话中存储所有令牌,或者是否有更好的解决方案才能使其正常工作?
是的,使用存储令牌方法,您必须保留所有生成的令牌,以防它们在任何时候重新进入.单个存储令牌不仅适用于多个浏览器选项卡/窗口,还适用于后退/前进导航.您通常希望通过使旧令牌到期来管理潜在的存储爆炸(根据年龄和/或自此发布的令牌数量).
另一种避免令牌存储的方法是发出使用服务器端秘密生成的签名令牌.然后,当您获得令牌时,您可以检查签名,如果匹配,您知道您已签名.例如:
// Only the server knows this string. Make it up randomly and keep it in deployment-specific // settings, in an include file safely outside the webroot // $secret= 'qw9pDr$wEyq%^ynrUi2cNi3'; ... // Issue a signed token // $token= dechex(mt_rand()); $hash= hash_hmac('sha1', $token, $secret); $signed= $token.'-'.$hash; ... // Check a token was signed by us, on the way back in // $isok= FALSE; $parts= explode('-', $_POST['formkey']); if (count($parts)===2) { list($token, $hash)= $parts; if ($hash===hash_hmac('sha1', $token, $secret)) $isok= TRUE; }
有了这个,如果你得到一个带有匹配签名的令牌,你知道你已经生成了它.这本身并没有多大帮助,但是除了随机性之外,您可以在令牌中添加额外的东西,例如用户ID:
$token= dechex($user->id).'.'.dechex(mt_rand()) ... if ($hash===hash_hmac('sha1', $token, $secret)) { $userid= hexdec(explode('.', $token)[0]); if ($userid===$user->id) $isok= TRUE
现在每个表单提交都必须由拿起表单的同一个用户授权,这几乎击败了CSRF.
放入令牌是一个好主意的另一件事是到期时间,因此瞬间客户端泄密或MitM攻击不会泄漏永远适用于该用户的令牌,以及密码重置时更改的值,因此更改密码会使现有令牌无效.