javascript Angular 对抗 Asp.Net WebApi,在服务器端实现 CSRF

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15574486/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 01:14:03  来源:igfitidea点击:

Angular against Asp.Net WebApi, implement CSRF on the server

javascriptasp.netangularjsasp.net-web-apicsrf

提问by dbruning

I'm implementing a website in Angular.js, which is hitting an ASP.NET WebAPI backend.

我正在 Angular.js 中实现一个网站,它正在访问 ASP.NET WebAPI 后端。

Angular.js has some in-built features to help with anti-csrf protection. On each http request, it will look for a cookie called "XSRF-TOKEN" and submit it as a header called "X-XSRF-TOKEN" .

Angular.js 有一些内置功能来帮助进行反 csrf 保护。在每个 http 请求中,它会寻找一个名为 "XSRF-TOKEN" 的 cookie 并将其作为一个名为 "X-XSRF-TOKEN" 的标头提交。

This relies on the webserver being able to set the XSRF-TOKEN cookie after authenticating the user, and then checking the X-XSRF-TOKEN header for incoming requests.

这依赖于 Web 服务器能够在对用户进行身份验证后设置 XSRF-TOKEN cookie,然后检查传入请求的 X-XSRF-TOKEN 标头。

The Angular documentationstates:

角文档状态:

To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called XSRF-TOKEN on first HTTP GET request. On subsequent non-GET requests the server can verify that the cookie matches X-XSRF-TOKEN HTTP header, and therefore be sure that only JavaScript running on your domain could have read the token. The token must be unique for each user and must be verifiable by the server (to prevent the JavaScript making up its own tokens). We recommend that the token is a digest of your site's authentication cookie with salt for added security.

为了利用这一点,您的服务器需要在第一个 HTTP GET 请求时在名为 XSRF-TOKEN 的 JavaScript 可读会话 cookie 中设置一个令牌。在随后的非 GET 请求中,服务器可以验证 cookie 是否与 X-XSRF-TOKEN HTTP 标头匹配,因此请确保只有在您的域上运行的 JavaScript 才能读取令牌。令牌对于每个用户必须是唯一的,并且必须可由服务器验证(以防止 JavaScript 组成自己的令牌)。我们建议令牌是您站点的身份验证 cookie 的摘要,并添加盐以增加安全性。

I couldn't find any good examples of this for ASP.NET WebAPI, so I've rolled my own with help from various sources. My question is - can anyone see anything wrong with the code?

我找不到 ASP.NET WebAPI 的任何好的示例,因此我在各种来源的帮助下推出了自己的示例。我的问题是 - 任何人都可以看到代码有什么问题吗?

First I defined a simple helper class:

首先我定义了一个简单的辅助类:

public class CsrfTokenHelper
{
    const string ConstantSalt = "<ARandomString>";

    public string GenerateCsrfTokenFromAuthToken(string authToken)
    {
        return GenerateCookieFriendlyHash(authToken);
    }

    public bool DoesCsrfTokenMatchAuthToken(string csrfToken, string authToken) 
    {
        return csrfToken == GenerateCookieFriendlyHash(authToken);
    }

    private static string GenerateCookieFriendlyHash(string authToken)
    {
        using (var sha = SHA256.Create())
        {
            var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(authToken + ConstantSalt));
            var cookieFriendlyHash = HttpServerUtility.UrlTokenEncode(computedHash);
            return cookieFriendlyHash;
        }
    }
}

Then I have the following method in my authorisation controller, and I call it after I call FormsAuthentication.SetAuthCookie():

然后我在我的授权控制器中有以下方法,我在调用 FormsAuthentication.SetAuthCookie() 后调用它:

    // http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks
    // http://docs.angularjs.org/api/ng.$http
    private void SetCsrfCookie()
    {
        var authCookie = HttpContext.Current.Response.Cookies.Get(".ASPXAUTH");
        Debug.Assert(authCookie != null, "authCookie != null");
        var csrfToken = new CsrfTokenHelper().GenerateCsrfTokenFromAuthToken(authCookie.Value);
        var csrfCookie = new HttpCookie("XSRF-TOKEN", csrfToken) {HttpOnly = false};
        HttpContext.Current.Response.Cookies.Add(csrfCookie);
    }

Then I have a custom attribute which I can add to controllers to make them check the csrf header:

然后我有一个自定义属性,我可以将其添加到控制器以让它们检查 csrf 标头:

public class CheckCsrfHeaderAttribute : AuthorizeAttribute
{
    //  http://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc
    protected override bool IsAuthorized(HttpActionContext context)
    {
        // get auth token from cookie
        var authCookie = HttpContext.Current.Request.Cookies[".ASPXAUTH"];
        if (authCookie == null) return false;
        var authToken = authCookie.Value;

        // get csrf token from header
        var csrfToken = context.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();
        if (String.IsNullOrEmpty(csrfToken)) return false;

        // Verify that csrf token was generated from auth token
        // Since the csrf token should have gone out as a cookie, only our site should have been able to get it (via javascript) and return it in a header. 
        // This proves that our site made the request.
        return new CsrfTokenHelper().DoesCsrfTokenMatchAuthToken(csrfToken, authToken);
    }
}

Lastly, I clear the Csrf token when the user logs out:

最后,我在用户注销时清除 Csrf 令牌:

HttpContext.Current.Response.Cookies.Remove("XSRF-TOKEN");

Can anyone spot any obvious (or not-so-obvious) problems with that approach?

任何人都可以发现这种方法有任何明显(或不那么明显)的问题吗?

采纳答案by dbruning

Haven't had any problems pointed out with the code, so I consider the question answered.

代码中没有指出任何问题,所以我认为问题已得到解答。

回答by vittore

Your code seems to be fine. The only thing is, you don't need most of the code you have as web.api runs "on top" of asp.net mvc, and latter has built in support for anti-forgery tokens.

你的代码似乎没问题。唯一的问题是,您不需要大部分代码,因为 web.api 在 asp.net mvc 的“顶部”运行,而后者内置了对防伪令牌的支持。

In comments dbrunning and ccorrin express concerns that you only able to use build in AntiForgery tokens only when you are using MVC html helpers. It is not true. Helpers can just expose session based pair of tokens that you can validate against each other. See below for details.

在评论中 dbrunning 和 ccorrin 表达了您只能在使用 MVC html 帮助程序时才能使用内置 AntiForgery 令牌的担忧。这不是真的。助手可以只公开基于会话的令牌对,您可以相互验证。详情请见下文。

UPDATE:

更新:

There is two methods you can use from AntiForgery:

您可以从 AntiForgery 使用两种方法:

  • AntiForgery.GetTokensuses two out parameters to return cookie token and form token

  • AntiForgery.Validate(cookieToken, formToken)validates if pair of tokens is valid

  • AntiForgery.GetTokens使用两个 out 参数返回 cookie token 和 form token

  • AntiForgery.Validate(cookieToken, formToken)验证令牌对是否有效

You totally can repurpose those two methods and use formToken as headerToken and cookieToken as actual cookieToken. Then just call validate on both within attribute.

您完全可以重新利用这两种方法,并使用 formToken 作为 headerToken 和 cookieToken 作为实际的 cookieToken。然后只需在属性内调用验证。

Another solution is to use JWT (check eg MembershipRebootimplementation)

另一种解决方案是使用 JWT(检查例如MembershipReboot实现)

This linkshows how to use built in anti-forgery tokens with ajax:

此链接显示了如何在 ajax 中使用内置的防伪令牌:

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

    $.ajax("api/values", {
        type: "post",
        contentType: "application/json",
        data: {  }, // JSON data goes here
        dataType: "json",
        headers: {
            'RequestVerificationToken': '@TokenHeaderValue()'
        }
    });
</script>


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);
}

Also take a look at this question AngularJS can't find XSRF-TOKEN cookie

也看看这个问题AngularJS can't find XSRF-TOKEN cookie

回答by Kolchy

I think your code is flawed. The whole idea around prevent CSRF is to prevent a unique token on each REQUEST, not each session. If the anti-forgery token is a session persisted value, the ability to perform CSRF still remains. You need to provide a unique token on each request...

我认为你的代码有缺陷。围绕阻止 CSRF 的整个想法是阻止每个请求上的唯一令牌,而不是每个会话。如果防伪令牌是会话持久值,则执行 CSRF 的能力仍然存在。您需要为每个请求提供一个唯一的令牌...

回答by Dibran

This solution isn't secure since CSRF attacks are still possible as long as the Auth cookie is valid. Both the auth and the xsrf cookie will be sent to the server when an attacker makes you perform a request via another site, and therefore you are still vulnerable until the user does a "hard" logout.

此解决方案并不安全,因为只要 Auth cookie 有效,CSRF 攻击仍然可能发生。当攻击者让您通过另一个站点执行请求时,auth 和 xsrf cookie 都将发送到服务器,因此在用户执行“硬”注销之前,您仍然容易受到攻击。

Each request or session should have its own unique token to truly prevent CRSF attacks. But probably the best solution is to not use cookie based authentication but token based authentication such as OAuth. This prevents other websites from using your cookies to perform unwanted requests, since the tokens are used in http headers instead of cookies. And http headers are not automatically send.

每个请求或会话都应该有自己唯一的令牌,以真正防止 CRSF 攻击。但可能最好的解决方案是不使用基于 cookie 的身份验证,而是使用基于令牌的身份验证,例如 OAuth。这可以防止其他网站使用您的 cookie 来执行不需要的请求,因为令牌用于 http 标头而不是 cookie。并且 http 标头不会自动发送。

  1. Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
  2. AngularJS Token Authentication using ASP.NET Web API 2, Owin, and Identity
  1. 使用 ASP.NET Web API 2、Owin 和 Identity 的基于令牌的身份验证
  2. 使用 ASP.NET Web API 2、Owin 和 Identity 的 AngularJS 令牌身份验证

These excellent blog posts contain information of how to implement OAuth for WebAPI. The blog posts also contains great information of how to integrate it with AngularJS.

这些优秀的博客文章包含有关如何为 WebAPI 实现 OAuth 的信息。博客文章还包含有关如何将其与 AngularJS 集成的重要信息。

Another solution might be to disable CORS and only accept incoming requests from whitelisted domains. However this won't work for non-website applications, such as mobile and/or desktop clients. Next to that once your website is vulnerable to a XSS attack the attacker will still be able to forge requests on your behalve.

另一种解决方案可能是禁用 CORS 并仅接受来自白名单域的传入请求。但是,这不适用于非网站应用程序,例如移动和/或桌面客户端。除此之外,一旦您的网站容易受到 XSS 攻击,攻击者仍然可以代表您伪造请求。