为什么普遍把预防CSRF令牌饼干?

问题描述 投票:222回答:4

我想了解与CSRF和适当的方式,以防止它整个问题。 (参考资料我已经阅读,理解并同意:OWASP CSRF Prevention CHeat SheetQuestions about CSRF。)

据我所知,周围的CSRF漏洞是由(从网络服务器的角度来看)在传入的HTTP请求的有效的会话cookie反映了一个验证用户的意愿的假设介绍。但对于原始域的所有cookie奇迹般地被安装到浏览器的要求,所以真的所有的服务器可以从一个有效的会话cookie的请求存在推断是请求来自其中有一个验证会话浏览器;它不能进一步假设有关在浏览器中运行任何代码,或是否真正反映了用户的愿望。为了防止这种情况的方法是将包括在请求中附加认证信息(“CSRF令牌”),由不是浏览器的自动cookie处理一些其它手段进行。宽泛地说,然后,会话cookie对用户进行认证/浏览器和CSRF令牌认证在浏览器中运行的代码。

所以,简单地说,如果你使用的会话cookie来验证你的web应用程序的用户,你应该还添加了一个CSRF令牌每个响应,并要求在每个(变异)要求匹配的CSRF令牌。该CSRF令牌然后进行从服务器到浏览器返回到服务器往返,证明是发出请求的页面被批准的服务器(由产生,偶数)该服务器。

在我的问题,这是关于用于对往返该CSRF令牌特定的传输方法。

看来常见(如AngularJSDjangoRails)从服务器发送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令牌下游似乎相当不理想给我。

security cookies web csrf owasp
4个回答
226
投票

一个很好的理由,你有几分触及,是一旦CSRF饼干已收到,它则可以在这两个常规形式和AJAX的POST使用客户端脚本整个应用程序使用。这将使意义在JavaScript重应用程序,如一个由AngularJS使用(使用AngularJS不需要应用程序将是一个单页的应用程序,那么,国家需要在CSRF值不同页面请求之间流动,将是有益通常不能在浏览器中仍然存在)。

考虑为你介绍每一种方法的优点和缺点的典型应用下面的场景和过程。这些都是基于Synchronizer Token Pattern

Request Body Approach

  1. 用户成功登录英寸
  2. 服务器问题AUTH的cookie。
  3. 用户点击导航到的形式。
  4. 如果没有这个环节产生的,服务器生成令牌CSRF,将其存储对用户会话,并输出到一个隐藏字段。
  5. 用户提交表单。
  6. 服务器检查隐藏字段匹配会话保持令牌。

好处:

  • 容易实现。
  • 工程与AJAX。
  • 工程与形式。
  • 饼干其实是可以HTTP Only

缺点:

  • 所有表格必须输出HTML隐藏字段。
  • 任何AJAX帖还必须包括值。
  • 该页面必须事先知道它需要CSRF令牌,因此它可以包含在网页内容,因此所有页面都必须包含该令牌价值的地方,这可能让时间来实施一个大型网站消费。

Custom HTTP Header (downstream)

  1. 用户成功登录英寸
  2. 服务器问题AUTH的cookie。
  3. 用户点击导航到的形式。
  4. 在浏览器的页面加载,那么AJAX请求以获取CSRF令牌。
  5. 服务器生成CSRF令牌(如果不是会话已产生的),将其存储针对用户会话并将其输出到一个标头。
  6. 用户提交表格(令牌经由隐藏字段发送)。
  7. 服务器检查隐藏字段匹配会话保持令牌。

好处:

  • 工程与AJAX。
  • cookie可以是HTTP Only

缺点:

  • 没有一个AJAX请求来获取标头值不起作用。
  • 所有表格必须加入它的HTML值动态。
  • 任何AJAX帖还必须包括值。
  • 该页面必须首先以AJAX请求得到CSRF令牌,所以它每次将意味着额外的往返。
  • 还不如干脆输出的令牌这将节省额外的请求的页面。

Custom HTTP Header (upstream)

  1. 用户成功登录英寸
  2. 服务器问题AUTH的cookie。
  3. 用户点击导航到的形式。
  4. 如果没有这个环节产生的,服务器生成令牌CSRF,将其存储针对网页内容的地方用户会话,并将其输出。
  5. 用户通过AJAX提交表格(令牌经由头中发送)。
  6. 服务器将检查自定义标题会保持令牌相匹配。

好处:

  • 工程与AJAX。
  • cookie可以是HTTP Only

缺点:

  • 不能与形式的工作。
  • 所有的AJAX帖子必须包含头。

Custom HTTP Header (upstream & downstream)

  1. 用户成功登录英寸
  2. 服务器问题AUTH的cookie。
  3. 用户点击导航到的形式。
  4. 在浏览器的页面加载,那么AJAX请求以获取CSRF令牌。
  5. 服务器生成CSRF令牌(如果不是会话已产生的),将其存储针对用户会话并将其输出到一个标头。
  6. 用户通过AJAX提交表格(令牌经由头中发送)。
  7. 服务器将检查自定义标题会保持令牌相匹配。

好处:

  • 工程与AJAX。
  • cookie可以是HTTP Only

缺点:

  • 不能与形式的工作。
  • 所有的AJAX帖还必须包括值。
  • 该页面必须首先以AJAX请求得到CSRF令牌,所以它每次将意味着额外的往返。

Set-Cookie

  1. 用户成功登录英寸
  2. 服务器问题AUTH的cookie。
  3. 用户点击导航到的形式。
  4. 服务器生成令牌CSRF,将其存储对用户会话并将其输出到一个cookie。
  5. 用户通过AJAX或通过HTML表单提交表单。
  6. 服务器将检查自定义页眉(或隐藏表单域)匹配的会话保持令牌。
  7. Cookie是在浏览器中的附加AJAX和形式要求使用无额外请求服务器检索CSRF令牌。

好处:

  • 容易实现。
  • 工程与AJAX。
  • 工程与形式。
  • 不一定需要一个AJAX请求来获取cookie的值。任何HTTP请求可以检索它,它可以被附加到通过JavaScript所有形式/ AJAX请求。
  • 一旦CSRF令牌已被检索,因为它是存储在cookie中的值可以在不额外的请求重复使用。

缺点:

  • 所有表格必须加入它的HTML值动态。
  • 任何AJAX帖还必须包括值。
  • 该Cookie会为每个请求提交(即,所有获取的图像,CSS,JS等,未参与CSRF过程)增加请求大小。
  • Cookie无法HTTP Only

所以该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防范。


47
投票

使用cookie来提供CSRF令牌传递给客户端不允许一个成功的攻击,因为攻击者无法读取cookie的值,因此不能把它放在服务器端验证CSRF需要它的人。

攻击者就可以导致服务器的请求与两个身份验证令牌cookie,并在请求头CSRF的cookie。但服务器是不是找的CSRF令牌作为请求头一个cookie,它看起来在请求的有效载荷。即使攻击者知道放在哪里了CSRF令牌的有效载荷,他们将不得不读取它的值把它放在那里。但浏览器的跨域策略阻止读取来自目标网站的任何cookie的值。

同样的逻辑并不适用于身份验证令牌的cookie,因为服务器是期望它在请求头和攻击者不必做什么特别的把它放在那里。


8
投票

我最好的猜测答案:请考虑如何让CSRF从服务器到浏览器令牌下这3个选项。

  1. 在请求主体(未HTTP标头)。
  2. 在自定义HTTP标头,不设置Cookie。
  3. 作为一个cookie,在一个Set-Cookie头。

我认为,第一一个,请求主体(同时通过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头所携带的饼干,作为一种方法,很容易在所有情况下使用(任何人的服务器就可以设置每个请求的饼干头,它不会不管什么样的数据是在所述请求机构)。因此,尽管它的缺点,它是框架广泛实施的最简单的方法。


1
投票

除了会话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);
}
© www.soinside.com 2019 - 2024. All rights reserved.