AJAX POST到Web API控制器和CSRF

iCo*_*ode 5 javascript ajax asp.net-mvc jquery asp.net-web-api

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

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

@functions{
public string GetTokenHeaderValue()
{
    string cookieToken, formToken;
    AntiForgery.GetTokens(null, out cookieToken, out formToken);
    return cookieToken + ":" + formToken;
}
}
Run Code Online (Sandbox Code Playgroud)

我在View中有这个隐藏的输入,但是意识到这很糟糕,因为它既有'form token',也有与AntiForgery.Validation一起使用的cookie令牌:

<input type="hidden" id="antiforgeryToken" value="@GetTokenHeaderValue()" />
Run Code Online (Sandbox Code Playgroud)

然后,在一个单独的JavaScript文件中(不在View中的脚本标记中),我正在对我的Web API Controller进行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) {
    }
});
Run Code Online (Sandbox Code Playgroud)

注意:在正文中获取POST的数据全部位于自定义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);
}
Run Code Online (Sandbox Code Playgroud)

AntiForgery.Validate验证令牌.

我看过这篇SO帖子,它给了我一些想法,但并没有为我解决这个问题.很多功劳归功于ASP.net上的这篇文章.

是什么让我与众不同(我认为)是我的JavaScript在一个单独的文件中,无法调用服务器端的Razor函数来获取防伪标记,对吧?关于如何在没有表格的情况下防范CSRF的任何想法?

Rud*_*ser 2

您应该制定 CSRF 保护来做到这一点,防止跨站点请求伪造。

CSRF 的快速概述如下:

跨站点请求伪造 (CSRF) 是一种攻击,它迫使最终用户在其当前经过身份验证的 Web 应用程序上执行不需要的操作。CSRF 攻击专门针对状态更改请求,而不是窃取数据,因为攻击者无法查看对伪造请求的响应。在社会工程的帮助下(例如通过电子邮件或聊天发送链接),攻击者可能会诱骗 Web 应用程序的用户执行攻击者选择的操作。

以下是针对未受保护的 Web API 的 CSRF 攻击示例:

<form action="http://yourapi.com/api/DeleteAccount">
    <input type="text" name="id" />
    <input type="submit" />
</form>
Run Code Online (Sandbox Code Playgroud)

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

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

正如您正确地陈述和实施的那样,使用防伪令牌可以防止这种情况,因为生成的令牌在 Web 响应中发送到客户端,然后由客户端发送回并进行验证以确保它是来自我们期望的某个地方的允许请求。与不记名令牌一样,第三方可能不知道您在服务器请求中发送的令牌。在这方面,您所实现的绝对正确,并且将令牌传递给客户端正是我们期望的工作方式。

在 ASP.NET MVC(无 API)中,其实现方式如下:

<form .. />
    @Html.AntiForgeryToken()
</form>
Run Code Online (Sandbox Code Playgroud)

..并通过以下方式验证服务器端:

[HttpPost]  
[ValidateAntiForgeryToken]  
public ActionResult Action(MyModel model)
{
    // ..
}
Run Code Online (Sandbox Code Playgroud)

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

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

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