为多个浏览器选项卡中的PHP中的CSRF攻击防止CSRF攻击



我知道,有两个针对CSRF攻击的主要解决方案。

  • 每个会话一个令牌
  • 所有唯一形式的令牌

我选择了第二个,但是有问题。如果访问者在两个选项卡中打开表单,则第二个选项卡上的表单令牌覆盖了前一个令牌。我写了一个解决这个问题的代码。我的问题是:这是一个很好的解决方案,还是我必须努力?(示例中最重要的部分。)

##### CONFIG & FUNCTION.PHP #####
// INITIALIZING - RUN ONLY ONCE
$_SESSION["csrf_tokens"]["postcomment"]     = array ();
$_SESSION["csrf_tokens"]["postcommentedit"] = array ();
// etc, etc.
function makearandomtoken ()
{
   // a simple but secure way
   return bin2hex (openssl_random_pseudo_bytes (32));
}
##### POSTCOMMENT_FORM.PHP #####
// new form for comment under a post, so create a new token
$created_token = makearandomtoken ();
array_push ( $_SESSION["csrf_tokens"]["postcomment"], $created_token );
// the form (only with important parts)
print "<form>n";
print "<input type="hidden" name="token" value="$created_token">n";
print "</form>n";
##### POSTCOMMENT_EXECUTE.PHP #####
// get the token from POST variable
$received_token = filter_input (INPUT_POST, 'token', FILTER_UNSAFE_RAW);
// check it
if ( in_array($received_token, $_SESSION["csrf_tokens"]["postcomment"]) )
{
   // VALID token, disable it
   $token_index = array_search($received_token, $_SESSION["csrf_tokens"]["postcomment"]);
   unset ($_SESSION["csrf_tokens"]["postcomment"][$token_index]);
}
else
{
   // INVALID token -> CSRF attempt
   die (); // or do anything
}

如果您想要每个表单实例新令牌,我不会看到您的实现问题。用户合法地打开了两个选项卡,因此两个令牌都将被接受是有道理的。

至于CSRF保护,我认为这是完整的。第三站点无法在没有令牌的情况下模仿用户,并且具有两个或三个有效令牌的用户不会使攻击者更容易任务。

如果您编码前端,则可以将事件处理程序注册到文档onunload事件中,并让处理程序提出一个邮政请求,以告诉服务器已卸载的服务器。这样,后端可以从会话中删除令牌。不过,这可能比值得更多的麻烦,因为如果用户键入浏览器后退按钮,她将重新加载一个无效的令牌,并在提交表单时会被错误感到沮丧。