我想了解与CSRF和适当的方式,以防止它整个问题。 (参考资料我已经阅读,理解并同意:OWASP CSRF Prevention CHeat Sheet,Questions about CSRF。)
据我所知,周围的CSRF漏洞是由(从网络服务器的角度来看)在传入的HTTP请求的有效的会话cookie反映了一个验证用户的意愿的假设介绍。但对于原始域的所有cookie奇迹般地被安装到浏览器的要求,所以真的所有的服务器可以从一个有效的会话cookie的请求存在推断是请求来自其中有一个验证会话浏览器;它不能进一步假设有关在浏览器中运行任何代码,或是否真正反映了用户的愿望。为了防止这种情况的方法是将包括在请求中附加认证信息(“CSRF令牌”),由不是浏览器的自动cookie处理一些其它手段进行。宽泛地说,然后,会话cookie对用户进行认证/浏览器和CSRF令牌认证在浏览器中运行的代码。
所以,简单地说,如果你使用的会话cookie来验证你的web应用程序的用户,你应该还添加了一个CSRF令牌每个响应,并要求在每个(变异)要求匹配的CSRF令牌。该CSRF令牌然后进行从服务器到浏览器返回到服务器往返,证明是发出请求的页面被批准的服务器(由产生,偶数)该服务器。
在我的问题,这是关于用于对往返该CSRF令牌特定的传输方法。
看来常见(如AngularJS,Django,Rails)从服务器发送CSRF令牌客户端作为cookie(即在一个Set-Cookie标头),然后有JavaScript中的客户端刮出来的cookie和其附加作为一个单独的XSRF-TOKEN头中发送回服务器。
(另一种方法是通过例如Express,其中CSRF令牌由服务器生成被包括在经由服务器侧模板扩张响应主体,直接附接到代码/标记,将它供给回服务器,例如推荐的单作为一个隐藏的表单输入,这例子是一个做事的多个Web 1.0十岁上下的方式,但将归纳罚款更JS-重的客户端。)
为什么会如此普遍使用的Set-Cookie作为CSRF令牌下游运输/为什么这是一个好主意吗?我想所有这些框架的作者仔细考虑自己的选择,并没有得到这个错误。但乍一看,使用Cookie来解决什么本质上的Cookie相关的设计限制似乎愚蠢。事实上,如果你正在使用Cookie的往返运输(设置Cookie:头下游服务器告诉浏览器的CSRF令牌,和饼干:帧头的浏览器将其返回到服务器),你会重新引入该漏洞你正在努力解决。
我认识到,上面的框架不使用cookies整个往返的CSRF令牌;他们使用的下游设置Cookie,然后其他的东西(例如X-CSRF令牌头)上游,并且这样做封闭漏洞。但是,即使使用的Set-Cookie作为下游输送可能引起误解和危险的;现在浏览器将附加CSRF令牌,包括真正的恶意XSRF请求的每个请求;充其量发出请求大于它需要在最坏的情况有些善意的,但误导一块服务器代码实际上可能会尝试使用它,这将是非常糟糕的。而且,还因为在CSRF令牌的实际预期接收者客户端Javascript,这意味着这个cookie不能仅HTTP的保护。因此,在一个Set-Cookie头发送CSRF令牌下游似乎相当不理想给我。
一个很好的理由,你有几分触及,是一旦CSRF饼干已收到,它则可以在这两个常规形式和AJAX的POST使用客户端脚本整个应用程序使用。这将使意义在JavaScript重应用程序,如一个由AngularJS使用(使用AngularJS不需要应用程序将是一个单页的应用程序,那么,国家需要在CSRF值不同页面请求之间流动,将是有益通常不能在浏览器中仍然存在)。
考虑为你介绍每一种方法的优点和缺点的典型应用下面的场景和过程。这些都是基于Synchronizer Token Pattern。
好处:
缺点:
好处:
缺点:
好处:
缺点:
好处:
缺点:
好处:
缺点:
所以该cookie方法是相当动态提供一种简单的方法来检索cookie值(任何HTTP请求),并使用它(JS可以自动地将值添加到任何形式的,它可以在AJAX请求或者作为报头或作为可以采用形式的值)。一旦CSRF令牌已收到了会议,也没有必要去重新创造它为采用CSRF攻击没有检索此令牌的方法攻击者。如果恶意用户试图读取该用户的令牌CSRF以任何的这则上述方法将由Same Origin Policy来防止。如果恶意用户试图检索CSRF令牌服务器端(例如,通过curl
),那么此令牌将不会被关联到同一个用户帐户作为受害者的身份验证会话cookie将被从请求丢失(这将是攻击者的 - 因此,将不会与受害者的会话相关的服务器端)。
还有Synchronizer Token Pattern里面还有Double Submit Cookie CSRF防止方法,这当然需要使用Cookie储存类型CSRF令牌。这是比较容易实现,因为它不需要为CSRF令牌的任何服务器端的状态。当使用这种方法,并且该值是通过饼干提交照常与请求,但该值也重复在任一个隐藏字段或标题,它的攻击者无法复制作为实际上CSRF令牌可以是标准认证的cookie他们无法读取摆在首位的价值。这将建议然而,选择其他的cookie,比身份验证Cookie等,以便身份验证cookie可以通过被标记的HttpOnly固定。因此,这是另一种常见原因,为什么你会使用基于cookie的方法找到CSRF防范。
使用cookie来提供CSRF令牌传递给客户端不允许一个成功的攻击,因为攻击者无法读取cookie的值,因此不能把它放在服务器端验证CSRF需要它的人。
攻击者就可以导致服务器的请求与两个身份验证令牌cookie,并在请求头CSRF的cookie。但服务器是不是找的CSRF令牌作为请求头一个cookie,它看起来在请求的有效载荷。即使攻击者知道放在哪里了CSRF令牌的有效载荷,他们将不得不读取它的值把它放在那里。但浏览器的跨域策略阻止读取来自目标网站的任何cookie的值。
同样的逻辑并不适用于身份验证令牌的cookie,因为服务器是期望它在请求头和攻击者不必做什么特别的把它放在那里。
我最好的猜测答案:请考虑如何让CSRF从服务器到浏览器令牌下这3个选项。
我认为,第一一个,请求主体(同时通过the Express tutorial I linked in the question证明),并不像移植到各种各样的情况;不是每个人都动态地生成每个HTTP响应;在那里你最终需要把令牌中产生的反应可能有很大的不同(在一个隐藏的表单输入;在JS代码或其它JS代码的变量访问的片段;甚至在一个URL虽然这通常似乎是一个不好的地方把CSRF令牌)。因此,尽管可行的一些定制,#1是一个艰苦的地方做一个尺寸适合所有人的方法。
第二个,自定义标题,是有吸引力的,但实际上并没有工作,因为while JS can get the headers for an XHR it invoked, it can't get the headers for the page it loaded from。
这使得第三个,由一个Set-Cookie头所携带的饼干,作为一种方法,很容易在所有情况下使用(任何人的服务器就可以设置每个请求的饼干头,它不会不管什么样的数据是在所述请求机构)。因此,尽管它的缺点,它是框架广泛实施的最简单的方法。
除了会话cookie(这是一种标准),我不想使用额外的饼干。
我发现了一个建单页Web应用程序(SPA)时,对我的作品,有许多AJAX请求的解决方案。注:我使用的服务器端Java和客户端的JQuery,但没有神奇的东西,所以我觉得这个原则可以在所有流行的编程语言来实现。
我没有多余的饼干解决方法很简单:
存储这是(如果你想使用网络存储,而不是一个全球性的,多数民众赞成精品课程的)在一个全局变量一个成功的登录之后,由服务器返回的CSRF令牌。指示JQuery的每个AJAX调用提供一个X-CSRF-TOKEN报头。
主要“指标”页面中包含这段JavaScript代码片段:
// Intialize global variable CSRF_TOKEN to empty sting.
// This variable is set after a succesful login
window.CSRF_TOKEN = '';
// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
});
在successul登录,创建一个随机的(和足够长)CSRF令牌,其存储在服务器端的会话,并返回给客户端。过滤器通过比较X-CSRF-TOKEN标头值与存储在会话中的某些值(敏感)传入的请求:这些应该匹配。
敏感的AJAX调用(POST表单数据和GET JSON数据),并在服务器端过滤器捕获来,是下/ DataService的/ *路径。登录请求必须打不过滤,所以这些都是另一条路径。对于HTML,CSS,JS请求和图像资源也并非/ DataService的/ *路径上,因此不过滤。这些含有什么秘密,可以做任何伤害,所以这是好的。
@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
resp.sendError(401);
} else
chain.doFilter(request, response);
}