PHP-CSRF-如何使其在所有选项卡中工作?

PHP-CSRF-如何使其在所有选项卡中工作?,php,tabs,csrf,Php,Tabs,Csrf,在过去的几天里,我读到了关于如何防止CSRF攻击的文章。我将在每次页面加载中更新令牌,在会话中保存令牌,并在提交表单时进行检查 但是如果用户打开了我的网站的3个标签,而我只在会话中存储最后一个令牌,该怎么办?这将用另一个令牌覆盖令牌,某些post操作将失败 我是否需要在会话中存储所有令牌,或者是否有更好的解决方案使其工作?是的,使用存储令牌方法,您必须保留所有生成的令牌,以防它们在任何时候返回。单个存储的令牌不仅无法用于多个浏览器选项卡/窗口,也无法用于向后/向前导航。您通常希望通过使旧令牌过期

在过去的几天里,我读到了关于如何防止CSRF攻击的文章。我将在每次页面加载中更新令牌,在会话中保存令牌,并在提交表单时进行检查

但是如果用户打开了我的网站的3个标签,而我只在会话中存储最后一个令牌,该怎么办?这将用另一个令牌覆盖令牌,某些post操作将失败


我是否需要在会话中存储所有令牌,或者是否有更好的解决方案使其工作?

是的,使用存储令牌方法,您必须保留所有生成的令牌,以防它们在任何时候返回。单个存储的令牌不仅无法用于多个浏览器选项卡/窗口,也无法用于向后/向前导航。您通常希望通过使旧令牌过期(按时间和/或自过期以来发行的令牌数量)来管理潜在的存储爆炸

另一种完全避免令牌存储的方法是发布使用服务器端机密生成的签名令牌。然后,当您取回令牌时,您可以检查签名,如果签名匹配,您就知道您已经签名了。例如:

// 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;

<input type="hidden" name="formkey" value="<?php echo htmlspecialchars($signed); ?>">

...

// 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;
}
现在,每个表单提交都必须由拿起表单的同一用户授权,这在很大程度上击败了CSRF


另一个好主意是在令牌中加入一个到期时间,这样瞬间客户端泄露或MitM攻击不会泄漏一个令牌,该令牌将永远适用于该用户,并且密码重置时更改的值,因此,更改密码会使现有令牌无效。

您可以简单地使用一个令牌,该令牌对于当前会话甚至用户都是持久的(例如,用户密码的哈希值),并且不能由第三方确定(例如,使用用户IP的哈希值是不好的)


然后,您不必存储大量生成的令牌,除非会话过期(这可能需要用户再次登录),否则用户可以使用任意多的选项卡。

Er,是的,这将在全局范围内起作用。多个选项卡和多个窗口都可以。@b否,会话将被覆盖?如果我们在上面讨论的是签名令牌方法,则不会向会话写入任何内容。这就是要点:通过在隐藏字段中发送签名消息,您可以知道它们来自您,并且在将来作为提交返回给您时信任它们,而无需记住任何状态。您根本不需要会话。@ThiefMaster:因为应用程序(视图)的HTML输出部分不需要知道该字符串将包含什么内容:关注点分离。当您将字符串输出到HTML时,编码是正确的;在这种情况下跳过编码将是过早的优化,节省一个(琐碎的)编码调用,代价是(a)如果我将来更改令牌格式,我可能会出错,以及(b)查看代码时,仅从模板上看字符串是否安全并不明显。@Mike:如果需要对用户隐藏签名数据,则需要使用实际加密,这会使代码变得更复杂(并且输出会更大,因为它会被填充到一个块中)。PGP是避免自己实现加密的陷阱的好方法(例如,如果您使用的是PHP,请参阅)。这是一个相当高的开销,虽然。。。你有那么在乎隐藏用户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