71

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

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

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

Angular 文档指出:

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

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

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

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

然后我在我的授权控制器中有以下方法,我在调用 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);
    }

然后我有一个自定义属性,我可以将其添加到控制器中以使它们检查 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);
    }
}

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

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

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

4

4 回答 4

8

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

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

更新:

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

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

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

您完全可以重新利用这两种方法并将 formToken 用作 headerToken 并将 cookieToken 用作实际的 cookieToken。然后只需在属性内调用 validate 即可。

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

此链接显示如何使用带有 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);
}

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

于 2013-05-10T01:16:05.763 回答
0

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

于 2016-04-07T02:40:28.390 回答
0

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

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

  1. 使用 ASP.NET Web API 2、Owin 和 Identity 的基于令牌的身份验证
  2. 使用 ASP.NET Web API 2、Owin 和 Identity 的 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.

于 2016-04-11T09:58:18.380 回答
-5

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

于 2015-01-07T04:53:26.273 回答