AJAX POST 到 Web API 控制器和 CSRF

Posted

技术标签:

【中文标题】AJAX POST 到 Web API 控制器和 CSRF【英文标题】:AJAX POST to Web API Controller and CSRF 【发布时间】:2017-06-13 19:57:30 【问题描述】:

我基本上需要在作为 MVC 应用程序一部分的 Web API 控制器中防止跨站点请求伪造。我对任何想法持开放态度。此时,我有一个 MVC 视图,它使用 ArcGIS for javascript API 显示 Esri 地图。用户在地图上创建一条路线,路线和它经过的各种要素可以通过 AJAX POST 保存。视图没有表单。这是因为我发布到服务器的所有数据都在内存中,并且在屏幕上(或隐藏字段)不可见。

所以,我在 MVC 视图中有以下内容来获取防伪令牌:

@functions
public string GetTokenHeaderValue()

    string cookieToken, formToken;
    AntiForgery.GetTokens(null, out cookieToken, out formToken);
    return cookieToken + ":" + formToken;


我在视图中有这个隐藏的输入,但意识到这很糟糕,因为它同时具有用于 AntiForgery.Validation 的“表单令牌”和 cookie 令牌:

<input type="hidden" id="antiforgeryToken" value="@GetTokenHeaderValue()" />

然后,在一个单独的 JavaScript 文件中(不在视图中的脚本标记中),我正在对我的 Web API 控制器进行 Http POST。这是我将令牌添加到请求标头的地方:

var headers = ;
headers['RequestVerificationToken'] = $("#antiforgeryToken").val();
// Ajax POST to Web API Controller
$.ajax(
    async: true,
    url: '/api/RouteData',
    type: 'POST',
    headers: headers,
    data: requestData,
    dataType: 'json',
    error: function (xhr, statusText, errorThrown) 
        console.log('Error saving route data! ' + errorThrown);
    ,
    success: function (result) 
    
);

注意:在正文中发布的数据全部存储在自定义 Dojo 小部件内的内存中(因为该页面使用 ArcGIS for JavaScript 显示 Esri 地图)。由于用户没有输入数据,因此页面上没有表单。)

在 Web API 控制器中的服务器端将它们捆绑在一起:

[System.Web.Http.HttpPost]
[ResponseType(typeof(RouteData))]
public async Task<IHttpActionResult> PostRouteData(RouteDataViewModel routeDataVM)

    try
    
        HttpRequestMessage httpRequestMessage = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
        ValidateRequestHeader(httpRequestMessage);
    
    catch (Exception ex)
    
        _logger.Log(LogLevel.Error, ex, ex.Message);
        throw;
    
        // Now that we know user is who they say they are, perform update 


void ValidateRequestHeader(HttpRequestMessage request)

    string cookieToken = "";
    string formToken = "";

    IEnumerable<string> tokenHeaders;
    if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
    
        string[] tokens = tokenHeaders.First().Split(':');
        if (tokens.Length == 2)
        
            cookieToken = tokens[0].Trim();
            formToken = tokens[1].Trim();
        
    
    AntiForgery.Validate(cookieToken, formToken);

AntiForgery.Validate 用于验证令牌。

我看过这个SO post,它给了我一些想法,但并没有完全解决我的问题。这个post on ASP.net 也有很多功劳。

这对我来说(我认为)的不同之处在于我的 JavaScript 位于一个单独的文件中,并且无法调用服务器端 Razor 函数来获取防伪令牌,对吧?关于如何在没有表单的情况下防止 CSRF 的任何想法?

【问题讨论】:

【参考方案1】:

CSRF 保护正是您应该制定的,以防止跨站点请求伪造。

quick overview of CSRF 是这样的:

Cross-Site Request Forgery (CSRF) 是一种攻击,它会强制最终用户在当前已通过身份验证的 Web 应用程序上执行不需要的操作。 CSRF 攻击专门针对改变状态的请求,而不是窃取数据,因为攻击者无法看到对伪造请求的响应。借助一些社会工程学的帮助(例如通过电子邮件或聊天发送链接),攻击者可能会诱骗 Web 应用程序的用户执行攻击者选择的操作。

如果您的 Web API 未受到保护,以下将是针对您的 Web API 的 CSRF 攻击示例:

<form action="http://yourapi.com/api/DeleteAccount">
    <input type="text" name="id" />
    <input type="submit" />
</form>

通过从受感染的网站发布此表单,如果您使用 cookie 身份验证(例如)作为管理帐户登录到您的 Web API,它很可能能够从您的系统中删除帐户。

发生这种情况是因为当您被重定向到您的 API 时,您的浏览器正在传递针对 yourapi.com 域存储的 cookie,从而验证用户并执行所需的操作。此攻击向量不同于使用不记名令牌等方式向您的 API 进行身份验证,因为第三方不知道此令牌。

正如您正确陈述和实施的那样,使用防伪令牌可以防止这种情况发生这是来自我们期望的某个地方的允许请求。与不记名令牌一样,第三方可能不知道您在服务器请求中发送的令牌。在这方面,您实现的内容是绝对正确的,将令牌传递给客户端正是我们期望它工作的方式。

在 ASP.NET MVC(无 API)中实现如下:

<form .. />
    @html.AntiForgeryToken()
</form>

.. 和经过验证的服务器端:

[HttpPost]  
[ValidateAntiForgeryToken]  
public ActionResult Action(MyModel model)

    // ..

当您调用@Html.AntiForgeryToken() 时,该方法既会设置一个cookie(包含cookie 令牌)并将其发送到客户端,还会生成一个&lt;input type="hidden" ..&gt; 标记以发送到客户端。然后将它们发送回去并在服务器端进行验证。

有了这些知识,您可以看到您对发送“cookie 令牌”和“表单令牌”的担忧是没有根据的,因为无论如何都会发生这种情况。

我确实看到您担心的原因,但您试图缓解的似乎是中间人 (MitM) 攻击,而不是 CSRF。要解决大部分中间人攻击,您应该确保您的站点/API 都通过 HTTPS 运行。如果您的客户端仍然容易受到中间人攻击,那么攻击者可能最不关心您的 API。

【讨论】:

您的解释非常有帮助和透彻。我能够围绕 MVC 对 Html.AntiForgeryToken 的使用进行思考,但对使用 AntiForgery.GetTokens 时我实际保护的内容感到困惑,因为该方法与令牌使用的方法略有不同。 没问题@iCode - 如果你的Web API和MVC应用在同一个域,你仍然可以使用@Html.AntiForgeryToken()并通过JS拉出表单令牌,cookie令牌当然会被发送在下一个请求本质上是针对该域的 cookie。更多详细信息来自您的链接答案:***.com/a/4074289/698179 啊,是的。我的 Web API 和 MVC 应用程序位于同一个域中。现在我已经完全理解了。事实上,我可以创建一个简单的表单来生成表单令牌和 cookie(包含 cookie 令牌)。在那种情况下,我会使用 JS 将表单令牌插入到我的 AJAX 帖子的数据参数中。我想这可以添加到我已经设置为 POST 数据的 JSON 对象之外。我将对其进行测试以了解这两种方法。 如果我的 web api 和前端位于不同的域中该怎么办。我无法将令牌从 web api 传递到前端。有什么解决方案吗?【参考方案2】:

经过与其他人的讨论,看来我实施的解决方案实际上还可以。是的,两个标记都在隐藏的输入字段中。但是,由于 CSRF 试图防止的性质 - another site 试图代表您发布 - 这个解决方案工作得很好。如果我在我的站点上,经过身份验证并浏览到另一个试图代表我进行 POST 的站点,则该站点将没有必要的令牌。

【讨论】:

以上是关于AJAX POST 到 Web API 控制器和 CSRF的主要内容,如果未能解决你的问题,请参考以下文章

跨域 ajax POST 到 web-api 总是返回空响应

来自 ASP NET Web API 的 CORS 阻止 Ajax POST 调用 - 对预检请求的响应未通过访问控制检查

JQuery Ajax POST 到 Web API 返回 405 Method Not Allowed

来自 ajax 的 Web api Post 调用失败

Ajax 将复杂且大的对象发布到 Web API 控制器非常慢

Ajax POST 到 Laravel API 偶尔会被 CORS 阻止