【发布时间】:2015-02-11 23:24:44
【问题描述】:
我的 MVC 应用程序有常用的 ajax 方法(在 web api 和常规控制器中)。我想根据呼叫来自我的应用程序的哪个区域(视图)来授权这些呼叫。我面临的问题是如何验证 ajax 调用的来源。
我意识到这并不容易,因为 ajax 调用很容易被欺骗,但由于我可以完全控制视图的呈现方式(完整页面源代码),也许有一种方法可以嵌入防伪类型令牌,可以稍后被验证为 Url Referrer。
身份验证已经处理,我可以安全地验证调用的身份,唯一的问题是验证调用来自哪个 URL(MVC 路由)。更具体地说,防止用户能够欺骗 ajax 调用的来源。
我尝试创建一个自定义授权标头并在视图渲染和 ajax 调用之间传递它,这很有效,但仍然很容易被欺骗(因为用户可以从站点的另一部分嗅探标头并重新使用它们)。最后我不确定如何安全地验证标头没有被欺骗。唯一想到的是对令牌内的原始上下文的一些信息进行编码,并以某种方式针对传入调用上下文(在 ajax 调用中传递令牌的上下文)对其进行验证。
我看到 MVC 具有 AntiForgery 令牌功能,但我不确定这是否能解决我的问题。如果是这样,我想知道如何使用它来验证 /api/common/update 是从 /home/index 与 /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);
}
那么我的防伪附加数据提供者如下所示:
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;
}
这很有效,因为我可以看到在提供程序中登录的正确页面,并且防伪在没有令牌的情况下中断。
但是,除非我做错了什么,否则这似乎是微不足道的恶搞。例如 如果我去pageA并复制令牌表单pageB(只是表单令牌,甚至不是cookie令牌),这仍然成功,并且在我的日志中我看到pageB同时从pageA执行ajax方法
确认这很容易被欺骗。
我正在使用 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;
}
然后,我将表单令牌与每个 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);
这一切都有效(更改有关令牌的任何内容都会引发正确的异常),然后在我的 IAntiForgeryAdditionalDataProvider 中,我可以看到它认为它正在处理的内容。
一旦我从另一个视图覆盖 csrf 令牌,它就会认为它就是那个视图。我什至不必篡改 UrlReferrer 来打破这个:/
如果我可以强制 cookie 在每次页面加载时都不同,这可能会起作用
【问题讨论】:
-
经典 XY 问题...
-
@DavidPeden 我已经概述了我的问题,并对如何以不同方式解决问题持开放态度。我还概述了我试图避免的其他可能的解决方案。也就是说,您的评论不是很有建设性
-
很抱歉你有这种感觉。我发布这不是为了刻薄,而是让任何其他对阅读您的问题有足够兴趣的人有机会考虑您所问的不是您真正的意思(正如在@eol 的假设和您随后的 cmets 中明确讨论的那样)。我碰巧同意他的一般建议,即使你试图避免它。看起来你已经把自己画到了一个角落里,有一些我认为应该受到挑战的基本假设。如果这样做成本太高,我完全理解并且没有进一步的建议。
-
好的,谢谢。是的,我完全有可能陷入死胡同,但这就是为什么我在这里发帖以验证我的方法中没有遗漏任何东西。这是一次非常有教育意义的经历;)
-
正在重新考虑这一点,并有这样的想法:即使您可以验证 ajax 请求来自您的源页面,如何阻止恶意用户在该页面上实时编辑您的 JavaScript 以调用不同的api(或具有不同参数的相同api)?我认为您假设如果请求来自您的页面,则必须允许请求本身,因为您在该页面上编写了 JavaScript……但我认为这不是一个有效的假设。
标签: asp.net-mvc asp.net-mvc-4 asp.net-web-api csrf