70

我一直在浏览一个开发项目的错误日志,发现以下错误(更改名称以保护有罪的无辜者)-

提供的防伪令牌用于用户“”,但当前用户是“管理员”。

这不是一个特别难以重现的问题-

  1. 在登录页面打开应用程序
  2. 在登录之前,在同一台计算机上的同一浏览器中打开第二个窗口或选项卡到登录页面
  3. 在第一个窗口中登录(或者实际上是第二个,顺序无关紧要)
  4. 尝试在剩余的登录窗口中登录

堆栈跟踪是-

System.Web.Mvc.HttpAntiForgeryException (0x80004005):提供的防伪令牌用于用户“”,但当前用户是“管理员”。在 System.Web.Helpers.AntiXsrf.AntiForgeryWorker.Validate(HttpContextBase httpContext) 在 System.Web.Helpers.AntiForgeryWorker.Validate(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken) 在 System.Web.Helpers.AntiForgery。 Validate() 在 System.Web.Mvc.ValidateAntiForgeryTokenAttribute.OnAuthorization(AuthorizationContext filterContext) 在 System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 过滤器, ActionDescriptor actionDescriptor) 在 System.Web.Mvc.Async.AsyncControllerActionInvoker。 <>c__DisplayClass25.

登录方法签名是-

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
...
}

这与互联网“ASP.NET MVC 4 Web 应用程序”模板项目中的方法签名完全相同,这表明微软要么认为 ValidateAntiForgeryToken 是必要的/最佳实践,要么只是在此处添加了该属性,因为它已被使用其他地方。

显然,我无法此方法中处理该问题,因为它没有达到,ValidateAntiForgeryToken 是一个预请求过滤器,它在请求到达控制器之前阻止它。

我可以在提交表单之前检查用户是否通过 Ajax 进行了身份验证,如果是,则尝试重定向到他们,或者只是删除该属性。

问题是这样的-我知道令牌的设计目的是在用户已经针对您的站点进行身份验证时阻止来自另一个站点 (CSRF) 的请求,因此在此基础上从定义将使用的表单中删除它是一个问题未经身份验证的用户

据推测,此实例中的属性旨在减轻恶意行为者为您的应用程序提供虚假登录表单的风险(尽管在抛出异常时可能用户已经输入了他或她将被记录的详细信息 - 但它可能会提醒他们一些事情是错的)。否则,从外部站点向表单提交不正确的凭据肯定会导致与站点本身完全相同的结果吗?我不依赖客户端验证/清理来清理潜在的不安全输入。

其他开发人员是否遇到过这个问题(或者我们是否有非常有创意的用户),如果是这样,您是如何解决/缓解它的?

更新:这个问题在 MVC5 中仍然存在,完全是故意的,现在出现错误消息“提供的防伪令牌是针对与当前用户不同的基于声明的用户。” 使用默认模板和身份提供程序时。Microsoft Developer Evangelist 和 Troy 的 PluralSight 同事 Adam Tuliper 在登录页面上的 Anti forgery token 上提供了一个相关的问题和有趣的答案,建议只需删除该令牌。

4

4 回答 4

62

我刚刚在ASafaWeb上测试了这种模式,结果相同(它使用相同的默认实现)。这就是我认为正在发生的事情

  1. 两种登录表单都加载相同的 __RequestVerificationToken cookie(它只是在同一个 DOM 中设置的相同)和相同的 __RequestVerificationToken 隐藏字段。这些令牌被键入给匿名用户。
  2. 登录表单 A 使用上述令牌发布,验证它们然后返回一个身份验证 cookie,该 cookie 现在位于浏览器 DOM 中
  3. 登录表单 B使用上述令牌发布,但现在它也使用登录表单 A 设置的身份验证 cookie 发布。

问题是令牌没有在步骤 3 中验证,因为它被键入到匿名用户,但它是由经过身份验证的用户在请求中传递的。这就是您看到错误的原因:提供的防伪令牌用于用户“”,但当前用户是“管理员”

您之所以遇到此问题,是因为表单 B 在表单 A 发布之前加载,因此表单 B 预计将由匿名用户发布。

从定义上将由未经身份验证的用户使用的表单中删除它是否有问题?

防伪令牌保护的主要潜在风险是 CSRF,它通常利用经过身份验证的用户,因为他们的浏览器可能被欺骗发出的任何请求都将自动附带一个身份验证 cookie,因此该操作将在他们的代表。这种风险在登录表单上不存在,因为通常用户没有经过身份验证,最糟糕的 CSRF 情况是登录被伪造然后失败;您并没有完全代表用户转账!

不过,防伪令牌还有其他优点:例如,它可以防止暴力攻击实际执行该方法并命中数据库。您需要确定您是否不太担心这一点,而更担心您在问题中遇到的情况。如果在防伪验证发生之前请求中已经存在 auth cookie,或者您需要在某处进入请求管道并采取措施。

坦率地说,我不确定我是否看到了问题。要重现此问题,用户必须同时打开多个登录表单,然后尝试连续登录每个表单 - 这真的会发生足以担心吗?当它发生时,第二次登录返回一个自定义错误页面真的很重要吗(当然你会在生产中这样做)?恕我直言,保留默认行为。

于 2013-10-01T07:29:15.063 回答
13

针对 AntiForgeryToken 运行的验证代码还会检查您登录的用户凭据是否没有更改——这些凭据也在 cookie 中进行了加密。这意味着,如果您在弹出窗口或其他浏览器选项卡中登录或注销,您的表单提交将失败,并出现以下异常:

System.Web.Mvc.HttpAntiForgeryException (0x80004005):
The provided anti-forgery token was meant for user "", but the current user is "SomeOne".

您可以通过设置AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;来关闭它。在Global.asax 文件中的Application_Start方法中。

当 AntiForgeryToken 未验证您的网站时,将抛出System.Web.Mvc.HttpAntiForgeryException类型的异常。您可以通过捕获HttpAntiForgeryException至少为用户提供针对这些异常的更多信息页面,从而使这更容易一些。

private void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();

if (ex is HttpAntiForgeryException)
  {
    Response.Clear();
    Server.ClearError(); //make sure you log the exception first
    Response.Redirect("/error/antiforgery", true);
  }
}
于 2015-09-17T07:31:17.900 回答
10

好的,如果我的假设不正确,请纠正我。

当抛出此异常时,您希望让用户继续,同时还可能显示相关的消息/警告?因为当这个异常被抛出时,我们已经可以知道当前用户是否通过了身份验证——我们可以访问请求。

那么,为什么不能接受呢?

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
...
}

/// <summary>
/// Handle HttpAntiForgeryException and redirect if user is already authenticated
/// </summary>
/// <param name="filterContext"></param>
/// <remarks>
/// See: http://stackoverflow.com/questions/19096723/login-request-validation-token-issue
/// </remarks>
protected override void OnException(ExceptionContext filterContext)
{
    base.OnException(filterContext);

    var action = filterContext.RequestContext.RouteData.Values["action"] as string;
    var controller = filterContext.RequestContext.RouteData.Values["controller"] as string;

    if ((filterContext.Exception is HttpAntiForgeryException) &&
        action == "Login" &&
        controller == "MyController" &&
        filterContext.RequestContext.HttpContext.User != null &&
        filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
    {
        LogManager.GetCurrentClassLogger().Warn(
            "Handled AntiForgery exception because user is already Authenticated: " +
                filterContext.Exception.Message, filterContext.Exception);

        filterContext.ExceptionHandled = true;

        // redirect/show error/whatever?
        filterContext.Result = new RedirectResult("/warning");
    }
}

我在这里遗漏了一些明显的东西吗?如果它有一些我没有看到的含义,我会删除这个答案。

于 2015-06-02T15:48:39.460 回答
-4

我有同样的问题,我通过将 machineKey 添加到 system.web 解决了它>

<machineKey validationKey="your machine key numbers" 
decryptionKey="Your description key nuumbers" validation="SHA1" decryption="AES" />

这是一个生成唯一机器密钥的站点 http://www.developerfusion.com/tools/generatemachinekey/

效果很好,我知道在登录页面中可能不需要 Anti Forgery,但如果有人决心入侵您的网站,很高兴知道 [ValidateAntiForgeryToken] 可以阻止他们。

于 2014-02-13T00:03:08.777 回答