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

Posted

技术标签:

【中文标题】Angular 针对 Asp.Net WebApi,在服务器上实现 CSRF【英文标题】:Angular against Asp.Net WebApi, implement CSRF on the server 【发布时间】:2013-03-12 13:18:06 【问题描述】:

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

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

这依赖于网络服务器能够在验证用户身份后设置 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://***.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");

谁能发现这种方法有任何明显(或不太明显)的问题?

【问题讨论】:

我也在尝试解决这个问题,并且想知道当攻击者都可以更改它们时比较两个 cookie 是否可以?如果你的盐被发现了,那么这不会受到损害吗? BenCr,只有在我的域上运行的 javascript 才能读取 cookie 并将其放入标头中。所以如果有恶意网站导致浏览器向我的网站提交请求,该请求将没有标头,因此它将拒绝该请求。 你能解释一下你在这里描述的解决方案的结果吗?它是如何失败的?还是您要我们找出安全漏洞? 只是在寻找评论。它不会失败(AFAIK) 对于所有未来的用户,这是一个有用的链接,以防您正在使用 Asp.net MVC and AngularJs 【参考方案1】:

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

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

更新:

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

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

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

【讨论】:

asp.net mvc 中的防伪支持依赖于使用 mvc 生成您的 html,因此它可以将请求验证令牌作为隐藏字段放入您的 HTML 表单中。我没有使用 mvc,因此我的 html 表单没有那个令牌。 @dbruning 只是辅助生成令牌,你可以在任何地方使用它 也许吧。我不记得确切的细节,但我找不到一个干净的方法来请求 csrf cookie。内置的 AntiForgery 方法似乎想要使用表单,而我只是使用 POST 的 JSON 数据。如果您可以分享一种获取 csrf cookie 的干净方法,则可以替换我上面的 CsrfTokenHelper 类。您仍然需要一种很好的方法来在传出请求上设置 cookie 并检查传入请求的标头。 对于不想在视图中使用 MVC 的人,MVC 助手不是一个选项。很多人希望保留他们的客户端代码纯 HTML/JS 以利用多个平台,并使用诸如 phonegap 之类的工具。如果你的观点是剃刀你在这方面的限制。 @ccorrin 你关注我的链接了吗?有ajax case的选项,你可以使用它。【参考方案2】:

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

【讨论】:

OWASP says 每个会话都这样做是标准的【参考方案3】:

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

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

    Token Based Authentication using ASP.NET Web API 2, Owin, and Identity AngularJS Token Authentication using ASP.NET Web API 2, Owin, and Identity

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

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

【讨论】:

这是不正确的。恶意网站无法使浏览器设置 X-XSRF-TOKEN header. 这似乎是 Angular 的 CookieXSRFStrategy 的工作方式:owasp.org/index.php/…。我打算将此策略用于 REST api。【参考方案4】:

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

【讨论】:

以上是关于Angular 针对 Asp.Net WebApi,在服务器上实现 CSRF的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core Web API + Angular 仿B站 目的分析以及创建 WebAPI + Angular7 项目

如何制作针对 ASP.NET WebAPI 进行身份验证的客户端?

如何使用角度修改 asp.net 核心 WebAPI 的 asp.net 身份 UI

ASP.NET 核心 Web API 和 Angular 客户端中的外部身份验证

asp.net mvc 6 web api + angular资源发布数组作为对象属性

如何从 asp.net core webapi 获取数据到连接到数据库的 angular 11.0.0。我试图这样做,但它返回空记录