【问题标题】:The anti-forgery cookie token and form field token do not match when using WebApi使用 WebApi 时防伪 cookie 令牌和表单字段令牌不匹配
【发布时间】:2018-01-05 11:15:19
【问题描述】:

我有一个单页应用程序(用户加载一堆 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.value 和 tokenHeader 都不为空。

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

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

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

@Html.AntiForgeryToken()

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

这可能是什么原因造成的?

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

提供的防伪令牌用于用户“”,但当前用户是“someuser@domain.com”。几秒钟前

当用户在登录时刷新他们的 SPA 实例时会发生这种情况。然后 SPA 出于某种原因将它们放入登录页面而不是内部页面(User.Identity.IsAuthenticated 为真) - 然后他们无法登录因为这个错误。清爽将它们拉回室内。不知道这意味着什么,但我认为更多信息不会有什么坏处。

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

【问题讨论】:

  • 防伪令牌在 cookie 上使用超时,所以这可能是原因。
  • @TasosK。我一直在监视它,它似乎与超时无关 - 几天后我成功验证了令牌,并在几秒钟或几分钟后看到失败。
  • @SB2055 你试过在登录方法上禁用缓存吗?
  • 不确定这是否会有很大帮助,但您能否说明您使用的是最新的网络 API,即Web API 2.2 - 5.2.3?
  • @SB2055 这有点牵强,但请尝试在您使用的登录方法上添加一个 {[OutputCache(NoStore=true, Duration = 0, VaryByParam= "None")]} 属性。

标签: asp.net asp.net-mvc asp.net-web-api csrf antiforgerytoken


【解决方案1】:

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

基本上,从浏览器到后端服务器的任何 AJAX 调用都会检查域来源(也就是加载脚本的域)。如果域匹配(JS 托管域 == 目标 AJAX 服务器域)AJAX 调用执行良好,否则返回 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>

运行它并查看安全错误:

跨域请求被阻止:同源策略不允许读取 http://www.reuters.com/ 的远程资源。 (原因:CORS 标头 'Access-Control-Allow-Origin' 缺失)。

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

现代浏览器通过实现 Web 来支持跨站点请求 应用程序(WebApps)工作组的跨站点访问控制 要求标准。

只要服务器配置为允许来自您的网络的请求 应用程序的来源, XMLHttpRequest 将工作。否则,一个 抛出 INVALID_ACCESS_ERR 异常。

【讨论】:

  • 哦哇——我不知道。我喜欢解决方案是删除内容。
  • 如果该解决方案对您有帮助,请不要犹豫验证 ;-)
  • 我现在正在进一步研究 - 似乎可能需要标头,但值不需要验证?我在这里看到一些相互矛盾的指导:security.stackexchange.com/questions/167064/…
  • “指南”说“您使用基本的 javascript XMLHttpRequest 对象来执行 GET 或 POST 请求。您很容易受到攻击,因为这可以使用 html 表单从跨站点执行等效请求”,即绝对错误。我将更新我的答案并包含一个代码 sn-p 以便您自己查看。
  • 更新完成。您可以信任 ASP.NET 开发人员,但没有人说您应该 ;-D
【解决方案2】:

我尝试给出相同的答案,如果在我们交换的 cmets 中,你的情况似乎与我的无关..

此类问题可能是由 XMLHttpRequest.setRequestHeader() 行为引起的,因为此函数“combines”是已在 http 请求上下文中分配的标头的值,如 MDN 所述和Whatwg:

如果这个方法多次调用同一个头文件, 值被合并到一个单独的请求标头中。

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

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

第一个 ajax POST 请求设置了一个清晰的标头 ("X-XSRF-Token"),因此,在服务器端,您应该有一个“有效”的标头值进行比较。

但是,在没有页面刷新或新的GET 请求的情况下,所有后续的 ajax POSTs,以及在MDN 和Whatwg 文档中所述,都会对相同的进行脏分配标头 ("X-XSRF-Token"),因为他们 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"););
}

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

更新 - 以下文本的修订: 因此,如果我们有一个 SPA 例如执行所有 ajax POSTs 为每个调用回收 XMLHttpRequest 对象并设置给定的 http 标头,在您的情况下: ...

【讨论】:

  • 这不可能。每当您创建一个新的 XMLHttpRequest 对象时,它都有一个与之关联的空标头列表。
  • 从 OP 来看,XMLHttpRequest 是每次还是一次都被实例化还不是很清楚。
  • 每次都有新请求,所以我不认为是这样:/
  • 好的.. 但是尝试在函数的主体中添加一个 console.log 以查看它是否被多次调用,并检查扩展过滤器 onActionExecuting 从标头令牌返回的内容
猜你喜欢
  • 1970-01-01
  • 2013-08-08
  • 1970-01-01
  • 2017-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-17
  • 2018-06-19
相关资源
最近更新 更多