PHP CSRF令牌随会话过期

时间:2018-02-02 03:23:52

标签: php jquery ajax session

我正在开发一个简单的网站,我正在尝试修复登录页面的问题,如果用户将页面打开几个小时后再返回并尝试使用它,那么csrf令牌不再有效,他们必须刷新页面才能再次开始正常工作。

所以匿名用户来到我的登录页面。该页面将生成一个csrf,如下所示:

if (empty($_SESSION['csrf_token']))
    $_SESSION['csrf_token'] = bin2hex(openssl_random_pseudo_bytes(32));

然后在我的表单中显示如下:

<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
到目前为止,这一切都很简单。这是当它开始下山时...当用户点击登录按钮时,会发出ajax请求,该请求使用会话变量检查csrf表单值:

if (!empty($_POST['csrf_token'])) {

    if (hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {

        // Success! They matched

    } else {
        // CSRF session token did not match posted form token
        die();
    }

} else {
    // CSRF token was missing from posted form
    die();
}

对于大多数用户来说,这工作正常,csrf令牌匹配并且他们登录但如果用户要进入登录页面,请将页面打开几个小时,然后回来会话将过期所以登录失败。从我猜测它失败,因为$ _SESSION ['csrf_token']是空的,因为它不再存在。这就是问题所在。

我找到了几个可行的解决方案,但没有沿着我正在寻找的路线走下去。一种解决方案是在用户最终使用以下内容恢复使用后刷新页面:

<META HTTP-EQUIV="REFRESH" CONTENT="csrf_timeout_in_seconds">

另一种解决方案是使用会话ID中的散列和服务器端秘密来创建csrf(但我认为这也会遇到同样的问题,因为session_id在尝试时不再存在检查csrf是否有效):

csrf = hash(sessionid+secret)

使用php cookie而不是php会话cookie会有问题吗?

还有其他替代解决方案吗?

2 个答案:

答案 0 :(得分:0)

我会考虑增加session.gc-maxlifetime中的php.ini,这将控制该会话何时到期。

PHP文档:http://php.net/manual/en/session.configuration.php#ini.session.gc-maxlifetime

此外,虽然如果用户的工作时间超过该设置,但这并不能解决问题,但我会在X时间后刷新登录页面而不进行任何交互。您还可以考虑让端点为该会话生成CSRF令牌,然后登录页面将发送ajax请求以获取令牌,您可以每隔X分钟调用该服务,这将更新会话值。

答案 1 :(得分:0)

最后回复此问题,因为我找到了一个可以在几天后运行的解决方案。

所以我最终需要的是一个令牌,其持续时间比会话长,但不会超过具有设定有效期的普通cookie。

经过相当多的研究后,我发现将csrf存储为cookie是很正常的做法,很多网站都是这样做的。

显然,我并不希望cookie是持久的,因为从理论上讲,如果黑客试图代表你提交数据(提交CSRF攻击),他们最终会破解正确的令牌。

为了避免这个问题,我创建了一个只在浏览器会话结束时才会过期的cookie - 这最终仍然会被破解,但如果关闭窗口会删除它,那么它的可能性就会降低。

我很可能会实施另一种策略来更频繁地清除csrf,但现在它可以正常工作

以下是我所拥有的:

if (empty($_COOKIE['csrf_token'])) {

    $cookie_csrf_name = 'csrf_token';
    $cookie_csrf_value =  bin2hex(random_bytes(32));
    $cookie_csrf_expiry = 0;
    $cookie_csrf_secure_only = FALSE;
    $cookie_csrf_http_only = TRUE;
    setcookie($cookie_csrf_name, $cookie_csrf_value, $cookie_csrf_expiry, '/', 'example.com', $cookie_csrf_secure_only, $cookie_csrf_http_only);

    /* I did have a problem where the cookie wouldn't be accessible without a refresh so to avoid this issue I found this line works (weirdly enough) */

    $_COOKIE[$cookie_csrf_name] = $cookie_csrf_value;

}