如何在MVC或Web Api ajax调用中验证referrer

Son*_*oul 8 asp.net-mvc csrf asp.net-mvc-4 asp.net-web-api

我的MVC应用程序有常见的ajax方法(在web api和常规控制器中).我想基于我的应用程序来自哪个区域(视图)来授权这些调用.我面临的问题是如何验证ajax调用的来源.

我意识到这不容易实现,因为ajax调用很容易被欺骗,但由于我可以完全控制视图的呈现方式(整页源代码),或许有一种方法可以嵌入防伪类型的标记,以后可以验证到网址推荐人.

身份验证已经处理完毕,我可以安全地验证呼叫的身份,唯一的问题是验证呼叫来自哪个URL(MVC路由).更具体地,防止用户欺骗ajax调用的起源.

我尝试创建一个自定义授权标头并在视图渲染和ajax调用之间传递它,这很有效,但仍然很容易欺骗(因为用户可以从网站的另一部分嗅探标头并重新使用它们).最后,我不确定如何安全地验证标头是否已被欺骗.我想到的唯一事情就是编写一些关于令牌内部原始上下文的信息,并以某种方式对传入的调用上下文(在ajax调用中传递令牌的那个)进行验证.

我看到MVC具有AntiForgery令牌功能,但我不确定这是否可以解决我的问题.如果是这样,我想知道它是如何用来验证/api/common/update/home/indexvs /user/setup调用的(这两个调用都是有效的).

同样,我想要一种方法来验证ajax调用来自哪个页面,并且用户身份不是问题.

更新

根据@Sarathy的推荐,我尝试实施防伪令牌.据我所知,通过在每个页面上添加带有令牌的隐藏字段,并将其与cookie中设置的令牌进行比较,我可以告诉它.以下是执行令牌验证的自定义操作过滤器属性的实现:

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var req = filterContext.RequestContext.HttpContext.Request;
        var fToken = req.Headers["X-Request-Verification-Token"];
        var cookie = req.Cookies[AntiForgeryConfig.CookieName];
        var cToken = cookie != null
            ? cookie.Value
            : "null";

        log.Info("filter \ntoken:{0} \ncookie:{1}", fToken, cToken);
        AntiForgery.Validate(cToken, fToken);
        base.OnActionExecuting(filterContext);
    }
Run Code Online (Sandbox Code Playgroud)

然后我的反伪造附加数据提供者看起来像这样:

public class MyAntiForgeryProvider : IAntiForgeryAdditionalDataProvider
{
    public string GetAdditionalData(System.Web.HttpContextBase context)
    {
        var ad  = string.Format("{0}-{1}",context.Request.Url, new Random().Next(9999));
        log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.AdditionalData: {0}", ad);
        log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.UrlReferrer: {0}", context.Request.UrlReferrer);
        return ad;
    }

    public bool ValidateAdditionalData(System.Web.HttpContextBase context, string additionalData)
    {
        log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData Request.Url: {0}", context.Request.Url);
        log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData additionalData: {0}", additionalData);
        return true;
    }
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为我可以看到提供商中记录的正确页面,并且防伪打破了令牌.

然而,除非我做错了什么,否则欺骗似乎微不足道.例如,如果我转到pageA并复制令牌表单pageB(只是表单令牌,甚至不是cookie令牌),这仍然成功,在我的日志中,我从pageA执行ajax方法时看到pageB

确认这很容易欺骗.

我正在使用csrf生成这样的ajax令牌:

    public static string MyForgeryToken(this HtmlHelper htmlHelper)
    {
        var c = htmlHelper.ViewContext.RequestContext.HttpContext.Request.Cookies[AntiForgeryConfig.CookieName];
        string cookieToken, formToken;
        AntiForgery.GetTokens(c != null ? c.Value : null, out cookieToken, out formToken);
        return formToken;
    }
Run Code Online (Sandbox Code Playgroud)

然后我通过每个ajax调用传回表单标记,并有一个自定义actionfilterattribute,我在其中读取/验证它以及cookie令牌

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var req = filterContext.RequestContext.HttpContext.Request;
        var fToken = req.Headers[GlobalConstants.AntiForgeKey];
        var cookie = req.Cookies[AntiForgeryConfig.CookieName];
        var cToken = cookie != null
            ? cookie.Value
            : "null";


        log.Info("MyAntiForgeryAttribute.OnActionExecuting. \ntoken:{0} \ncookie:{1}", fToken, cToken);
        AntiForgery.Validate(cToken, fToken);
Run Code Online (Sandbox Code Playgroud)

这一切都有效(改变关于令牌的任何内容会引发正确的异常),然后在我的IAntiForgeryAdditionalDataProvider中,我可以看到它认为它正在处理的内容.

一旦我从另一个视图覆盖csrf标记,它就认为它就是那个视图.我甚至不必篡改UrlReferrer来打破这个:/

如果我可以强制cookie在每个页面加载时不同,这种方法可行

Son*_*oul 1

我的快速临时解决方案是使用在每个页面加载时创建的自定义令牌(我在令牌缓存中跟踪的 GUID),这些令牌在所有 ajax 调用中作为标头传递。此外,我创建了一个原始 url 哈希并将其组合到自定义身份验证令牌中。然后,在我的 ajax 方法中,我提取哈希并将其与 UrlReferrer 哈希进行比较,以确保其未被篡改。由于自定义令牌总是不同的,因此不太明显猜测发生了什么,因为令牌在每个页面加载时似乎都不同。然而,这并不安全,因为只要付出足够的努力,就可以发现 url 哈希值。暴露程度在某种程度上是有限的,因为用户身份不是问题,所以最坏的情况是给定用户将获得对网站另一部分的写访问权限,但只能以他自己的身份获得。我的网站是内部网站,我正在审核每一个举动,因此任何发脾气的尝试都会很快被抓住。

我同时使用 jQuery 和 Angular,因此在所有请求中附加令牌,如下所示:

var __key = '@Html.GetHeaderKey()' //helper method to get key from http header
//jQuery
$.ajaxSetup({
    beforeSend: function (xhr, settings) {
    xhr.setRequestHeader('X-Nothing-To-See-Here', __key); // totally inconspicuous 
})

//angular
app.config(['$httpProvider', function ($httpProvider) {
  $httpProvider.defaults.headers.common['X-Nothing-To-See-Here'] = __key;
});
Run Code Online (Sandbox Code Playgroud)

更新

这种方法的缺点是自定义令牌需要在网络场或应用程序重新启动时保留。基于@Sarathy 的想法,我试图通过利用 MVC 防伪框架来回避这个问题。基本上添加/删除我的“盐”并让框架管理实际的令牌验证。这样我的管理工作就少了一些。一旦我确认这有效,将发布更多详细信息。