角对Asp.Net WebApi,在服务器端实现CSRF



我正在实现一个网站在Angular.js,这是击中一个ASP。. NET WebAPI后端

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

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

Angular文档中说:

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

我找不到任何关于ASP的好例子。NET WebAPI,所以我在各种来源的帮助下创建了自己的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");

有人能发现这种方法明显(或不太明显)的问题吗?

您的代码似乎很好。唯一的问题是,你不需要你拥有的大部分web代码。API运行在asp.net mvc之上,后者内置了对防伪令牌的支持。

在评论中,dbrunning和corrin表达了这样的担忧:只有当你使用MVC html helper时,你才能使用内置的防伪造令牌。这不是真的。helper可以公开基于会话的令牌对,您可以对它们进行相互验证。详情见下文

更新:

你可以使用AntiForgery中的两种方法:

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

  • 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不能't找到XSRF-TOKEN cookie

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

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

    基于令牌的ASP认证. NET Web API 2、Owin和Identity使用ASP的AngularJS令牌认证. NET Web API 2、Owin和Identity
这些优秀的博客文章包含了如何为WebAPI实现OAuth的信息。博客文章还包含了如何将其与AngularJS集成的大量信息。

另一个解决方案可能是禁用CORS,只接受来自白名单域的传入请求。然而,这并不适用于非网站应用程序,如移动和/或桌面客户端。其次,一旦你的网站容易受到XSS攻击,攻击者仍然能够以你的名义伪造请求。

我认为你的代码有缺陷。围绕防止CSRF的整个思想是防止在每个请求(而不是每个会话)上使用唯一令牌。如果防伪造令牌是会话持久值,则仍然可以执行CSRF。您需要为每个请求提供唯一的令牌…

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

最新更新