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


70

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

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

这取决于Web服务器能够在验证用户身份之后设置XSRF-TOKEN cookie,然后检查X-XSRF-TOKEN标头中是否有传入请求。

角文档状态:

要利用此优势,您的服务器需要在第一个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");

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


1
我也在尝试解决这个问题,并且想知道当攻击者可以同时更改两个cookie时是否可以比较两个cookie?如果发现您的盐,那么这不会妥协吗?
BenCr

BenCr,只有在我的域上运行的javascript才能读取cookie并将其放入标题中。因此,如果存在一个导致浏览器向我的网站提交请求的恶意网站,则该请求将没有标题,因此它将拒绝该请求。
dbruning

您能解释一下这里介绍的解决方案的结果吗?它怎么会失败?还是您要我们在安全性方面寻找漏洞?
user1852503

只是想发表评论。它不会失败(AFAIK)
2013年

2
对于所有将来的用户,如果您正在使用Asp.net MVC和AngularJs
shankbond

Answers:


8

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

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

更新:

您可以从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找不到XSRF-TOKEN cookie


11
asp.net mvc中的防伪支持依赖于使用mvc生成您的html,以便它可以将请求验证令牌作为隐藏字段放入HTML表单中。我没有使用mvc,因此我的html表单没有该标记。
dbruning

@dbruning它只是帮助程序生成令牌,您可以在任何地方使用它
vittore

1
也许。我不记得确切的细节,但是我找不到一种简单的方法来索要csrf cookie。内置的AntiForgery方法似乎要使用表单,而我只使用POST的JSON数据。如果可以共享一种干净的方法来获取csrf cookie,则可以替换上面的CsrfTokenHelper类。您仍然需要一种很好的方法来在传出请求上设置cookie并检查传入请求上的标头。
dbruning 2013年

6
对于不想将MVC用于其视图的人,MVC帮助器不是一个选择。许多人希望保留其客户端代码纯HTML / JS以利用多个平台,并使用诸如phonegap之类的工具。如果您的观点令人毛骨悚然,那么您在这方面的限制。
ccorrin

3
@ccorrin您关注我的链接了吗?有针对ajax case的选项,您可以使用它。
vittore

0

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


2
OWASP说 ,每个会话都这样做是标准的
dbruning

0

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

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

  1. 使用ASP.NET Web API 2,Owin和身份的基于令牌的身份验证
  2. 使用ASP.NET Web API 2,Owin和Identity的AngularJS令牌身份验证

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

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


3
那是不对的。恶意网站无法导致浏览器设置X-XSRF-TOKEN标头
dbruning

似乎是Angular的CookieXSRFStrategy的工作方式: owasp.org/index.php/…。我将对REST api使用此策略。
厘米

-5

代码没有指出任何问题,因此我认为问题已经解决。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.