标头中的CSRF令牌和请求中的Cookie不匹配 [英] CSRF Token in header and cookie do not match in requests

查看:0
本文介绍了标头中的CSRF令牌和请求中的Cookie不匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在实现无状态API,而我的组织说我需要防御CSRF攻击。

我在网上找到了这个人的解决方案,并决定尝试实现仅限客户端的方法:http://blog.jdriven.com/2014/10/stateless-spring-security-part-1-stateless-csrf-protection/

以下是该网站针对无状态解决方案所做的说明(以防网站关闭):

  1. 客户端生成的CSRF-令牌。让客户端在Cookie和自定义HTTP中生成并发送相同的唯一密码值 头球。考虑到网站只能读/写Cookie 对于它自己的域,只有真实站点才能在两者中发送相同的值 标题。使用这种方法,您的服务器要做的就是检查 在每个请求的无状态基础上,这两个值相等!

不幸的是,它不起作用。我的标头值从来不与我的Cookie值匹配,在某些情况下,我的标头似乎只落后于匹配Cookie值的一个请求。

这是我的角度代码:

 app.config(['$httpProvider', function ($httpProvider) {
     //fancy random token
     function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e16]+1e16).replace(/[01]/g,b)};

     $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
     $httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN';

     $httpProvider.interceptors.push(function () {
         return {
             'request': function (config) {
                 document.cookie = 'CSRF-TOKEN=' + b();
                 return config
             }
         };
     });
 }]);

以下是正在发送的CSRF值的一些示例。

 CSRF-TOKEN=d25cf03a985d575ad48a863eac91467666
 X-CSRF-TOKEN:fa1f165df8b27195a90f5e7841108f4e42

 CSRF-TOKEN=d25cf03a985d575ad48a863eac91467666
 X-CSRF-TOKEN:fa1f165df8b27195a90f5e7841108f4e42

 CSRF-TOKEN=9c8dd46ed06c250b707ac0cb80a08a23ac
 X-CSRF-TOKEN:d25cf03a985d575ad48a863eac91467666

 CSRF-TOKEN=eb407a0303c21173fe4d0ae03c97eaea6d
 X-CSRF-TOKEN:0cf066bf83e50b5c74cb932ab8a47c94e8

 CSRF-TOKEN=506355a940a2ac5b48f363712b34570d73
 X-CSRF-TOKEN:eb407a0303c21173fe4d0ae03c97eaea6d

这是怎么回事?我觉得我在做那家伙解决方案中的所有事情,但最终却得到了奇怪的结果。

推荐答案

我最终没有发送由客户端为每个请求创建的随机令牌。我无法让它工作。

我是这样解决我的问题的(某种程度上):

(1)对于每个请求(包括第一个请求),我从我的API在响应头中发回一个名为"XSRF-TOKEN"的cookie和一个与之相关的随机化值。这是AngularJS在使用其CSRF保护时默认查找的名称。

(2)收到令牌后在请求中发生的事情是,AngularJS使用该令牌的值在请求标头中发送名为"XSRF-TOKEN"的cookie,还使用名为"X-XSRF-TOKEN"的标头和该令牌的值。

所以我的API正在处理随机的XSRF令牌,而我的应用程序仍然是无状态的。我正在使用Web API,并使用全局过滤器来处理XSRF令牌的创建。下面是我的代码(用C#编写),用于执行此操作。我在UI中不再有任何代码来处理这一点(因为它似乎不需要它):

public class ValidateAntiForgeryToken : ActionFilterAttribute
{
    private const string XsrfCookieName = "XSRF-TOKEN";

    private const string XsrfHeaderName = "X-XSRF-TOKEN";

    private const string CsrfTokenSalt = "RANDOM SALT";


    public override void OnActionExecuting(HttpActionContext filterContext)
    {
        string requestMethod = filterContext.Request.Method.Method;

        Boolean isValid = true;

        if (requestMethod != "GET")
        {
            var headerToken = filterContext.Request.Headers.Where(x => x.Key.Equals(XsrfHeaderName, StringComparison.OrdinalIgnoreCase))
                .Select(x => x.Value).SelectMany(x => x).FirstOrDefault();

            var cookieToken = filterContext.Request.Headers.GetCookies().Select(x => x[XsrfCookieName]).FirstOrDefault();

            // check for missing cookie or header
            if (cookieToken == null || headerToken == null)
            {
                isValid = false;
            }

            // ensure that the cookie matches the header
            if (isValid && !String.Equals(headerToken, cookieToken.Value, StringComparison.OrdinalIgnoreCase))
            {
                isValid = false;
            }

            if (!isValid)
            {
                filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
                filterContext.Response.ReasonPhrase = "Unauthorized to make that request.";
                return;
            }
        }

        base.OnActionExecuting(filterContext);
    }


    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        string textToHash = RandomStringGeneration();
        string cookieText = HashService.HashText(textToHash, CsrfTokenSalt);

        var cookie = new CookieHeaderValue(XsrfCookieName, HttpUtility.UrlEncode(cookieText));

        /* don't use this flag if you're not using HTTPS */
        cookie.Secure = true;      
        cookie.HttpOnly = false; // javascript needs to be able to get this in order to pass it back in the headers in the next request

        /* if you have different environments on the same domain (which I did in one application using this code) make sure you set the path to be ApplicationPath of the request. Case sensitivity does matter in Chrome and IE, so be wary of that. */
        cookie.Path = "/";

        actionExecutedContext.Response.Headers.AddCookies(new[] { cookie });

        base.OnActionExecuted(actionExecutedContext);
    }
}

以下是我的HashService.HashText()代码:

public class HashService
{
    public static string HashText(string text, string salt)
    {
        SHA512Managed hashString = new SHA512Managed();

        byte[] textWithSaltBytes = Encoding.UTF8.GetBytes(string.Concat(text, salt));
        byte[] hashedBytes = hashString.ComputeHash(textWithSaltBytes);

        hashString.Clear();

        return Convert.ToBase64String(hashedBytes);
    }
}

希望这对未来无状态应用程序中的某些人有所帮助。遗憾的是,发回的令牌仅在Cookie值和标头值中与其自身进行比较。这是我现在能够验证它的唯一方法(我觉得这很安全)。我可能会为XSRF保护创建一个全新的表,并使用它来验证某个令牌是否确实是用户应该使用的令牌。这是使我的API保持无状态的唯一方法。

在阅读了AngularJS的$http文档后,我偶然发现了这个解决方案,其中规定:

跨站点请求伪造(XSRF)保护:XSRF是由 未经授权的站点可以获取您用户的私人数据。角形 提供了一种应对XSRF的机制。在执行XHR请求时, $http服务从Cookie读取令牌(默认情况下为XSRF-TOKEN) 并将其设置为HTTP头(X-XSRF-TOKEN)。因为只有Java脚本 可以读取Cookie,您的服务器可以是 确保XHR来自在您的域上运行的JavaScript。这个 不会为跨域请求设置标头。

要利用这一点,您的服务器需要在 第一个HTTP上名为XSRF-TOKEN的JavaScript可读会话Cookie 获取请求。在后续的XHR请求中,服务器可以验证 Cookie与X-XSRF-Token HTTP标头匹配,因此请确保 只有在您的域上运行的JavaScript才可能发送该请求。 令牌对于每个用户必须是唯一的,并且必须可由 服务器(以防止JavaScript编造自己的令牌)。我们 建议令牌是站点身份验证的摘要 添加盐的Cookie可提高安全性。

可以使用xsrfHeaderName和 $HTTPProvider.defaults的xsrfCookieName属性 配置时间、运行时的$HTTP.DEFAULTS或每个请求的配置 对象。

这篇关于标头中的CSRF令牌和请求中的Cookie不匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆