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

发布于 2024-08-30 03:17:15 字数 192 浏览 3 评论 0原文

最近几天我读到了如何防止 CSRF 攻击。我将在每个页面加载中更新令牌,将令牌保存在会话中,并在提交表单时进行检查。

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

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

I have read about how to prevent CSRF-attacks in the last days. I am going to update the token in every pageload, save the token in the session and make a check when submitting a form.

But what if the user has, lets say 3 tabs open with my website, and I just store the last token in the session? This will overwrite the token with another token, and some post-action is going to fail.

Do I need to store all tokens in the session, or is there a better solution to get this working?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

江南月 2024-09-06 03:17:15

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

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

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

放入令牌的另一个好主意是过期时间,这样短暂的客户端妥协或 MitM 攻击就不会泄漏将永远为该用户工作的令牌,以及密码重置时更改的值,因此更改密码会使现有令牌失效。

Yes, with the stored-token approach you'd have to keep all generated tokens just in case they came back in at any point. A single stored-token fails not just for multiple browser tabs/windows but also for back/forward navigation. You generally want to manage the potential storage explosion by expiring old tokens (by age and/or number of tokens issued since).

Another approach that avoids token storage altogether is to issue a signed token generated using a server-side secret. Then when you get the token back you can check the signature and if it matches you know you signed it. For example:

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

With this, if you get a token with a matching signature you know you generated it. That's not much help in itself, but then you can put extra things in the token other than the randomness, for example 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

Now each form submission has to be authorised by the same user who picked up the form, which pretty much defeats CSRF.

Another thing it's a good idea to put in a token is an expiry time, so that a momentary client compromise or MitM attack doesn't leak a token that'll work for that user forever, and a value that is changes on password resets, so that changing password invalidates existing tokens.

草莓味的萝莉 2024-09-06 03:17:15

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

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

You could simply use a token which is persistent for the current session or even the user (e.g. a hash of the hash of the user's password) and cannot be determined by a third party (using a hash of the user's IP is bad for example).

Then you don't have to store possibly tons of generated tokens and unless the session expires (which would probably require the user to login again anyway) the user can use as many tabs as he wants.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文