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

问题描述:

我在Angular.js中实现了一个网站,该网站正在访问ASP.NET WebAPI后端。Angular对Asp.Net WebApi,在服务器上实现CSRF

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

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

Angular documentation状态:

要利用这一点,你的服务器需要设置令牌叫XSRF-TOKEN的第一个HTTP GET请求一个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://*.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"); 

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

+1

我试图想出一个解决方案,以及想知道如果比较这两个饼干是好的,当他们都可以被攻击者改变?如果你的盐被发现,那么这是不是妥协? – BenCr 2013-10-25 11:03:16

+0

BenCr,只有在我的域上运行的JavaScript才能读取cookie并将其放入标题中。因此,如果存在导致浏览器向我的网站提交请求的恶意网站,请求将不会有头,因此它会拒绝该请求。 – dbruning 2013-10-28 05:11:08

+0

你能解释一下你在这里描述的解决方案的结果吗?它如何失败?或者你是否要求我们在安全方面找到漏洞? – user1852503 2013-11-12 01:02:58

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

你的代码似乎很好。唯一的问题是,当web.api运行在asp.net mvc的顶层时,你不需要大部分代码,而后者已经构建了对防伪令牌的支持。

在评论中,dbrunning和ccorrin表达了担心,只有当您使用MVC html助手时,才能够使用AntiForgery标记进行构建。这不是真的。助手可以公开基于会话的一对令牌,您可以互相验证。详情请参阅下文。

UPDATE:

有两种方法可以从防伪使用:

  • AntiForgery.GetTokens使用两个输出参数返回cookie的令牌,并形成令牌

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

您完全可以重新调整这两种方法的用途,并将formToken用作headerToken和cookieToken作为实际的cookieToken。然后在属性中调用validate。

另一种解决方案是使用智威汤逊(检查如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

+11

asp.net mvc中的防伪支持依赖于使用mvc生成您的html,以便它可以将请求验证令牌放入您的HTML表单中作为隐藏字段。我没有使用mvc,因此我的html表单没有这个标记。 – dbruning 2013-05-10 07:12:25

+0

@dbruning它只是帮手生成令牌,你可以在任何你想要的地方使用它 – vittore 2013-05-10 13:00:24

+1

也许。我不记得确切的细节,但我找不到一个干净的方式来请求csrf cookie。内置的AntiForgery方法似乎想要使用表单,而我只是使用POST'ed JSON数据。如果您可以共享一个干净的方式来获取csrf cookie,那可能会取代上面的CsrfTokenHelper类。 您仍然需要一个很好的方法来设置传出请求上的Cookie并检查传入请求上的标头。 – dbruning 2013-05-13 07:00:55

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

+2

[OWASP说](https://www.owasp.org/index.php/Anti_CSRF_Tokens_ASP.NET#ASP.NET_MVC_and_Web_API:_Anti-CSRF_Token)它是每个会话的标准做法 – dbruning 2016-06-16 01:52:09

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

每个请求或会话都应该有自己的唯一标记来真正防止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

这些优秀的博客帖子中包含如何实现OAuth认证的WebAPI的信息。博客文章还包含有关如何将其与AngularJS集成的很好的信息。

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

+3

这是不正确的。恶意网站无法让浏览器设置X-XSRF-TOKEN *标题*。 – dbruning 2016-06-16 01:54:20

+0

这似乎是Angular CookieXSRFStrategy的工作原理:https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers。我打算将这个策略用于REST API。 – 2017-06-16 21:16:34