【问题标题】:PHP - CSRF - How to make it works in all tabs?PHP - CSRF - 如何让它在所有选项卡中工作?
【发布时间】:2011-02-11 07:34:48
【问题描述】:

最近几天我已经阅读了有关如何防止 CSRF 攻击的信息。我将在每次页面加载中更新令牌,将令牌保存在会话中并在提交表单时进行检查。

但是如果用户有,假设我的网站打开了 3 个选项卡,而我只是将最后一个令牌存储在会话中?这将用另一个令牌覆盖该令牌,并且某些后操作将失败。

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

【问题讨论】:

    标签: php tabs csrf


    【解决方案1】:

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

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

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

    有了这个,如果你得到一个带有匹配签名的令牌,你就知道你生成了它。这本身并没有多大帮助,但是您可以在令牌中添加除随机性之外的额外内容,例如用户 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。

    放入令牌的另一件事是过期时间,这样一来,瞬间的客户端妥协或中间人攻击就不会泄漏将永远为该用户工作的令牌,并且值会发生变化密码重置,因此更改密码会使现有令牌无效。

    【讨论】:

    • 呃,是的,这将在全球范围内有效。多个选项卡和多个窗口都可以。
    • @bobince 否,会话将被覆盖?
    • 如果我们在讨论上面的签名令牌方法,则不会向会话写入任何内容。这就是重点:通过在隐藏字段中传递已签名的消息,您可以知道它们来自您,并在它们将来作为提交返回给您时信任它们,而无需记住任何状态。您根本不需要会话。
    • @ThiefMaster:因为应用程序的 HTML 输出部分(视图)不需要知道该字符串究竟包含什么:关注点分离。将字符串输出到 HTML 时,编码是正确的做法;在这种情况下跳过编码将是一个过早的优化,节省一个(微不足道的)编码调用,代价是(a)如果我将来更改令牌格式我可能会出错,并且(b)在查看代码时它是仅从模板来看字符串是安全的并不明显。
    • @Mike:如果您需要对用户隐藏签名数据,则需要使用实际加密,这是它变得更加毛茸茸的地方(并且输出会更大,因为它会被填充到一个块)。 PGP 是避免自己实现加密的陷阱的好方法(例如,如果您使用 PHP,请参阅GnuPG extension)。不过,这是一个相当高的开销……您是否那么在意隐藏用户 ID?
    【解决方案2】:

    您可以简单地使用对当前会话甚至用户持久的令牌(例如用户密码哈希的哈希)并且无法由第三方确定(使用用户 IP 的哈希是不好的例如)。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-09
      • 1970-01-01
      • 2021-05-24
      • 2022-12-01
      • 2018-04-28
      • 2012-03-28
      相关资源
      最近更新 更多