Javascript 针对Asp.NETWebAPI,在服务器上实现CSRF

Javascript 针对Asp.NETWebAPI,在服务器上实现CSRF,javascript,asp.net,angularjs,asp.net-web-api,csrf,Javascript,Asp.net,Angularjs,Asp.net Web Api,Csrf,我正在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-toke

我正在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的摘要,带有salt,以增加安全性

对于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;
        }
    }
}
然后我有一个自定义属性,我可以将其添加到控制器中,使它们检查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和ccorrin表达了这样的担忧:只有在使用MVC html帮助程序时,才能使用内置的反伪造令牌。这不是真的。助手可以公开基于会话的令牌对,您可以对它们进行相互验证。详情见下文

更新:

从AntiForgery可以使用两种方法:

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

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

您完全可以重新利用这两种方法,使用formToken作为headerToken,使用cookieToken作为实际cookieToken。然后只需在属性中对这两个属性调用validate

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

演示如何在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);
}

@功能{
公共字符串TokenHeaderValue()
{
字符串cookieToken,formToken;
antifforgery.GetTokens(null,out-cookieToken,out-formToken);
返回cookieToken+“:”+formToken;
}
}
$.ajax(“api/值”{
类型:“post”,
contentType:“应用程序/json”,
data:{},//JSON数据在这里
数据类型:“json”,
标题:{
“RequestVerificationToken”:“@TokenHeaderValue()”
}
});
void ValidateRequestHeader(HttpRequestMessage请求)
{
字符串cookieToken=“”;
字符串formToken=“”;
IEnumerable标记头;
if(request.Headers.TryGetValues(“RequestVerificationToken”,out-tokenHeaders))
{
string[]tokens=tokenHeaders.First().Split(“:”);
if(tokens.Length==2)
{
cookieToken=tokens[0]。Trim();
formToken=tokens[1].Trim();
}
}
防伪。验证(cookieToken,formToken);
}

也看这个问题

p>代码没有指出任何问题,所以我考虑回答的问题。

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

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

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

  • 这些优秀的博客文章包含了如何为WebAPI实现OAuth的信息。博客文章还包含了如何将其与AngularJS集成的大量信息


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

    asp.net mvc中的防伪支持依赖于使用mvc生成html,以便将请求验证令牌作为隐藏字段放入html表单中。我没有使用mvc,因此我的html表单没有该标记。@dbrunning它只是助手生成标记,您可以在任何地方使用它。我不记得确切的细节,但我找不到一个干净的方法来索要csrf饼干。内置的AntiForgery方法似乎想要处理表单,而我只是处理POST'ed JSON数据。如果你能分享一个干净的方法来获得csrf cookie,那就可以重新使用C
    <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);
    }