PHP - CSRF - 如何让它在所有选项卡中工作?
最近几天我已经阅读了有关如何防止 CSRF 攻击的信息.我将在每次页面加载中更新令牌,将令牌保存在会话中并在提交表单时进行检查.
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.
但是如果用户有,假设我的网站打开了 3 个标签,而我只是将最后一个令牌存储在会话中?这将用另一个令牌覆盖该令牌,并且某些后操作将失败.
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?
推荐答案
是的,使用存储令牌方法,您必须保留所有生成的令牌,以防它们在任何时候返回.单个存储令牌不仅对多个浏览器选项卡/窗口失败,而且对后退/前进导航也失败.您通常希望通过使旧令牌过期(按年龄和/或此后发行的令牌数量)来管理潜在的存储爆炸.
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;
}
有了这个,如果你得到一个带有匹配签名的令牌,你就知道你生成了它.这本身并没有多大帮助,但是您可以在令牌中添加除随机性之外的额外内容,例如用户 ID:
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
现在每个表单提交都必须由拾取表单的同一用户授权,这几乎击败了 CSRF.
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.
相关文章