【问题标题】:Many AJAX requests at once with CSRF protection一次使用 CSRF 保护的多个 AJAX 请求
【发布时间】:2017-09-01 08:22:37
【问题描述】:

大家好。

我的 Web 应用程序基于异步请求。 Timer 小部件正在工作并通过 AJAX 每秒更新它的状态(是的,这是必要的)。

我用每个 AJAX 发送我的 CSRF 令牌:

project_data.append(csrf_name_key,csrf_name_value);
project_data.append(csrf_value_key,csrf_value_value);

作为回应,我正在更新该全局变量:

function setCSRF(response) {
    csrf_name_key = response.nameKey;
    csrf_name_value = response.name;
    csrf_value_key = response.valueKey;
    csrf_value_value = response.value;      
}

一切都很好。但是,如果我将执行另一个 AJAX,例如当我将待办事项列表中的任务更改为“完成”时,它有时会以错误结束,因为我在从之前的请求中获取新令牌之前发送 AJAX。

我真的不知道如何解决这个问题。第一个想法是,我将使用 5 个不同的令牌制作“类似堆栈数组”,但一个 https 请求 = 一对令牌,我无法生成它。

也许是某种类型的 ajax 请求队列,但是在正确的时间执行它们会怎样 - 我不知道。

我的实际伪解决方案是“如果失败,最多再试 10 次”:

if(e.target.response=="Failed CSRF check!") {
    if(failedAjax<10) checkForSurvey();
    failedAjax++;
    return;
}

它通常可以工作,但控制台中出现错误,这是非常肮脏的解决方案。

我正在使用带有 CSRF 扩展的 Slim 3 微框架。真的请帮忙解决这个有趣的问题。

我将不胜感激,

亚瑟

【问题讨论】:

  • 我会给每个动作(定时器、更新待办事项等)自己的 csrf 令牌。
  • 但要生成令牌,您需要发出 http 请求。为了提出这个请求,您需要传递实际的令牌或等待 - 也许我会关闭“令牌生成器”路由的 CSRF 保护?这是保存解决方案吗?
  • 你不能在页面加载时发送初始令牌吗?
  • 您可以采用这种方法,因为您可以为每个会话库创建一个 CSRF 令牌数组,然后检查数组中是否存在传入的令牌。如果是,那么您可以验证请求,否则,每分钟触发另一个 ajax,这将从数组中删除旧的 csrf 令牌。但由于我不知道苗条框架,所以我不能建议你如何破解苗条来实现这个。我希望这可以达到您的目的
  • 那么你应该使用 CSRF 令牌来请求一个新的 csrf 令牌数组,正如 @AbhisekMalakar 所说如果你不担心性能,你可以让 ajax 请求等到新的 CSRF 令牌到达.

标签: javascript php ajax csrf slim


【解决方案1】:

通常,您可以通过不接受 cookie 进行身份验证来简单地消除 CSRF。

您可以将身份验证令牌保存在 localStorage 中,并将其作为每个请求的标头发送。

这样您就无需担心 CSRF 及其令牌。

【讨论】:

  • 你能解释一下吗?我认为在您的解决方案中,CSRF 只是不检查令牌并打开我的应用程序以进行跨站点攻击:)
  • CSRF 是基于浏览器自动发送每个请求的 cookie 的事实。如果网站使用 cookie 进行身份验证,那么攻击者可以将用户引导到他自己的网站并执行以下操作:被发送到服务器。如果该网站不接受该身份验证 cookie,而是使用标头进行身份验证 - 该攻击向量和所有 CSRF 攻击都将不起作用。
  • 好吧,我现在明白了,但不是 100% :C。它希望在实践中看起来如何?如何不接受该 cookie?
  • 我对slim不太了解,但是可能有一种方法可以在其配置中消除auth cookies。您可能需要考虑使用 JWT 进行令牌身份验证。斯利姆似乎支持它。 stackoverflow.com/questions/26379936/…discourse.slimframework.com/t/slim-3-with-json-web-token/209
  • 你也可以手动检查它,比如: if(isset($req->headers('X-Auth-Token')) { //authenticateWithToken } else { //go away }对不起,我的 PHP 技能很差。
【解决方案2】:

有一些选项供您选择:

  1. 在你的javascript代码中使用一堆csrf-tokens

  2. 使用可以多次使用的 csrf 令牌(不那么安全)

  3. 为请求使用队列

令牌堆栈

Slim-Csrf-middleware 为您提供功能,要生成这些令牌,您只需将它们发送到客户端即可。 你可以做一个 api 来获取 5 个 csrf 令牌,这个 api 也会消耗 csrf-token。

添加一个 api 并在那里生成令牌。

$app->get('/foo', function ($request, $response, $args) {
    // check valid  csrf token

    $tokens = [];
    for ($i = 0; $i < 5; $i++) {
        $tokens[] = $this->csrf->generateToken();
    }

    return $response->withJson($tokens);
});

现在 csrf-token 在整个用户会话中都有效。

Guard::generateToken() 返回如下内容:

array (size=2)
  'csrf_name' => string 'csrf58e669ff70da0' (length=17)
  'csrf_value' => string '52ac7689d3c6ea5d01889d711018f058' (length=32)

一个多用途的csrf-token

为此,Slim-Csrf 已经提供了令牌持久化模式的功能。这可以通过构造函数或Guard::setPersistentTokenMode(bool) 方法启用。在我的示例中,我正在使用以下方法:

$container['csrf'] = function ($c) {
    $guard = new \Slim\Csrf\Guard;
    $guard->setPersistentTokenMode(true);
    return $guard;
};

这里是来自persistanceTokenMode-attribute 的 PhpDoc

/**
 * Determines whether or not we should persist the token throughout the duration of the user's session.
 *
 * For security, Slim-Csrf will *always* reset the token if there is a validation error.
 * @var bool True to use the same token throughout the session (unless there is a validation error),
 * false to get a new token with each request.
 */

ajax 请求队列。

为请求添加一个队列,这可能会延迟您的请求的执行,但总会有一个有效的 csrf 令牌。

这应该被视为伪代码,因为我还没有测试过。

var requestQueue = [];
var isInRequest = false;

var csrfKey = ''; // should be set on page load, to have a valid token at the start
var csrfValue = '';

function newRequest(onSuccessCallback, data) { // add all parameters you need
    // add request to the queue
    requestQueue.push(function() {
        isInRequest = true;
        // add to csrf stuff to data
        $.ajax({
            data: xxx
            url: "serverscript.xxx",
            success: function(data) {
                // update csrfKey & csrfValue
                isInRequest = false;
                tryExecuteNextRequest(); // try execute next request
                onSuccessCallback(data); // proceed received data
            }
        }});
    );
    tryExecuteNextRequest();
}

function tryExecuteNextRequest() {
    if(!isInRequest && requestQueue.length != 0) { // currently no request running &
        var nextRequest = requestQueue.shift();
        nextRequest(); // execute next request
    }
}

【讨论】:

  • 非常感谢我使用了一个堆栈 :) 我认为我不能在页面加载时生成一个以上的令牌 :C 但一切都很好我在 js 中使用了 5 个令牌数组 :)
  • 你好。该 for 循环返回具有相同名称和其他值的令牌,并且 csrf 将最后定义的令牌视为正确,其他令牌无效并且系统返回失败的 csrf 检查......这是从一个请求生成 5 个其他有效令牌的任何方式吗?
猜你喜欢
  • 2019-01-25
  • 2011-01-21
  • 2015-10-06
  • 2015-10-15
  • 2022-10-17
  • 2014-02-19
  • 2011-02-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多