首页 文章

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

提问于
浏览
68

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

Angular.js具有一些内置功能,可以帮助防止csrf . 在每个http请求中,它将查找名为“XSRF-TOKEN”的cookie并将其作为名为“X-XSRF-TOKEN”的标头提交 .

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

Angular documentation说:

要利用这一点,您的服务器需要在第一个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 回答

  • 7

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

    在评论中,dbrunning和ccorrin表示担心只有在使用MVC html助手时才能使用AntiForgery令牌中的构建 . 这不是真的 . 帮助程序可以公开基于会话的令牌对,您可以相互验证这些令牌 . 请参阅下文了解详情 .

    更新:

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

    • AntiForgery.GetTokens 使用两个输出参数来返回cookie令牌和表单令牌

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

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

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

    This link显示了如何使用带有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

  • 0

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

  • -5

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

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

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

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

  • 0

    没有任何问题与代码指出,所以我认为问题的答案 .

相关问题