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

2022-08-30 23:14:31

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

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

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


答案 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;
}

这样,如果您获得具有匹配签名的令牌,则知道您生成了它。这本身并没有多大帮助,但是除了随机性之外,您还可以在令牌中放入额外的东西,例如user 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攻击不会泄漏永久适用于该用户的令牌,并且在密码重置时更改的值,因此更改密码会使现有令牌无效。


答案 2

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

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


推荐