使用 WebApi 时防伪 cookie 令牌和表单字段令牌不匹配

The anti-forgery cookie token and form field token do not match when using WebApi

我有一个单页应用程序(用户加载一堆 HTML/JS 然后发出 AJAX 请求而无需再次调用 MVC - 仅通过 WebAPI)。在 WebAPI 中,我有以下内容:

public sealed class WebApiValidateAntiForgeryTokenAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(
        System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        if (actionContext == null)
        {
            throw new ArgumentNullException(nameof(actionContext));
        }
        if (actionContext.Request.Method.Method == "POST")
        {
            string requestUri = actionContext.Request.RequestUri.AbsoluteUri.ToLower();
            if (uriExclusions.All(s => !requestUri.Contains(s, StringComparison.OrdinalIgnoreCase))) // place some exclusions here if needed
            {
                HttpRequestHeaders headers = actionContext.Request.Headers;

                CookieState tokenCookie = headers
                    .GetCookies()
                    .Select(c => c[AntiForgeryConfig.CookieName]) // __RequestVerificationToken
                    .FirstOrDefault();

                string tokenHeader = string.Empty;
                if (headers.Contains("X-XSRF-Token"))
                {
                    tokenHeader = headers.GetValues("X-XSRF-Token").FirstOrDefault();
                }

                AntiForgery.Validate(!string.IsNullOrEmpty(tokenCookie?.Value) ? tokenCookie.Value : null, tokenHeader);
            }

        }
        base.OnActionExecuting(actionContext); // this is where it throws
    }
}

注册于Global.asax:

    private static void RegisterWebApiFilters(HttpFilterCollection filters)
    {
        filters.Add(new WebApiValidateAntiForgeryTokenAttribute());
        filters.Add(new AddCustomHeaderFilter());
    }

偶尔,我会在日志中看到 The anti-forgery cookie token and form field token do not match 错误。发生这种情况时,tokenCookie.valuetokenHeader 都不为空。

客户端,我的所有 AJAX 请求都使用以下内容:

beforeSend: function (request) {
     request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
},

使用 Razor 在我的 SPA 页面上生成一次令牌:

@Html.AntiForgeryToken()

我在 Web.config 中设置了我的机器密钥。

可能是什么原因造成的?

更新 我刚刚检查了日志,有时我也看到了这个:

The provided anti-forgery token was meant for user "", but the current user is "someuser@domain.com". a few seconds ago

当用户在登录时刷新他们的 SPA 实例时会发生这种情况。然后 SPA 出于某种原因将它们放入登录页面而不是内页(User.Identity.IsAuthenticated 为真)- 然后他们可以' 因为这个错误而登录。提神将它们拉回内部。不确定这意味着什么,但我认为更多信息不会造成伤害。

附录 https://security.stackexchange.com/questions/167064/is-csrf-protection-useless-with-ajax/167076#167076

我试着给出同样的答案,如果在我们交换的评论中,你的情况似乎与我的不相关..

此类问题可能是由 XMLHttpRequest.setRequestHeader() 行为引起的,因为此函数“combines" the values of an header that has been already assigned in the context of an http request, as stated by MDN and Whatwg:

If this method is called several times with the same header, the values are merged into one single request header.

因此,如果我们有一个 SPA 例如执行所有 ajax POSTs 设置给定的 http header,在你的情况下:

beforeSend: function (request) {
     request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
}

第一个 ajax POST 请求设置了一个明确的 header ("X-XSRF-Token") 所以,服务器端,你应该有一个 "valid" header 要比较的值。

但是,如果没有页面刷新或新的 GET 请求,所有后续 ajax POSTs,以及 MDN and Whatwg documentation, will make a dirty assignment of the same header ("X-XSRF-Token"), because they combine 中所述的新请求旧值。

为避免此问题,您可以尝试重置 "X-XSRF-Token" 值(但相关文档不多,这似乎是一个不可靠的解决方案...)

beforeSend: function (request) {
     request.setRequestHeader("X-XSRF-Token", null);      //depends on user agents..
     //OR.. request.setRequestHeader("X-XSRF-Token", ''); //other user agents..
     //OR.. request.setRequestHeader("X-XSRF-Token");     //other user agents..
     request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
}

其他解决方案可以依赖于您必须自己实现的某些 client-side 状态处理机制,因为无法获取 http 请求的值或状态访问 headers (只能访问响应headers)。

更新 - 修订以下文字: 因此,如果我们有一个 SPA 例如执行所有 ajax POSTs 回收 XMLHttpRequest object 每次调用和设置给定的 http header,在你案件: ...

我的回答是建议不要尝试在 AJAX 调用中使用基于令牌的 CSRF 保护,而是依赖网络浏览器的原生 CORS 功能。

基本上,从浏览器到后端服务器的任何 AJAX 调用都会检查域来源(也就是从中加载脚本的域)。如果域匹配(JS 托管域 == 目标 AJAX 服务器域),AJAX 调用执行正常,否则 returns null

如果攻击者试图在他自己的服务器上托管恶意 AJAX 查询,如果您的后端服务器没有允许他这样做的 CORS 策略(默认情况下就是这种情况),那么它将失败。

因此,CSRF 保护在 AJAX 调用 中毫无用处,您可以 简单地不尝试处理它来降低技术债务.

有关 CORS - Mozilla Foundation

的更多信息

代码示例 - 使用控制台检查器!

<html>
<script>
function reqListener () {
  console.log(this.responseText);
}

var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.reuters.com/");
oReq.send();
</script>
</html>

运行 它并查看安全错误:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://www.reuters.com/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

Mozilla 对 Cross-site XMLHttpRequest 实现非常清楚:

Modern browsers support cross-site requests by implementing the Web Applications (WebApps) Working Group's Access Control for Cross-Site Requests standard.

As long as the server is configured to allow requests from your web application's origin, XMLHttpRequest will work. Otherwise, an INVALID_ACCESS_ERR exception is thrown.