AJAX POST到Web API控制器和CSRF

时间:2017-01-28 02:26:41

标签: javascript jquery ajax asp.net-mvc asp.net-web-api

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

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

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

我在视图中有这个隐藏的输入,但意识到这很糟糕,因为它有'形式标记'和用于AntiForgery的cookie标记。验证:

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

然后,在一个单独的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) {
    }
});

注意:正文中获得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);
}

AntiForgery.Validate验证令牌。

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

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

2 个答案:

答案 0 :(得分:2)

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

quick overview of CSRF是这样的:

  

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

以下是针对您的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,因为第三方不知道此令牌。

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

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

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

..并验证了服务器端:

[HttpPost]  
[ValidateAntiForgeryToken]  
public ActionResult Action(MyModel model)
{
    // ..
}

当您致电@Html.AntiForgeryToken()时,该方法都会设置一个Cookie(包含Cookie令牌)并将其发送给客户端,并生成<input type="hidden" ..>标记以发送给客户端。然后将这些发送回并验证服务器端。

有了这些知识,您可以看到您对发送&#34; cookie令牌&#34;和#34;形成令牌&#34;是没有根据的,因为无论如何都会发生这种情况。

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

答案 1 :(得分:1)

经过与他人的一些讨论后,看来我实施的解决方案实际上是正常的。是的,两个令牌都在隐藏的输入字段中。但是,根据CSRF试图防范的本质 - 另一个 网站尝试代表您发布 - 这个解决方案运行正常。如果我在我的网站上,经过身份验证并浏览到另一个尝试代表我发布POST的网站,则该网站将没有必要的令牌。