ChatGPT解决这个技术问题 Extra ChatGPT

为什么将 CSRF 预防令牌放在 cookie 中很常见?

我试图了解 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 作为往返传输(Set-Cookie:下游标头用于服务器告诉浏览器 CSRF 令牌,Cookie:上游标头用于浏览器将其返回给服务器)您将重新引入您的漏洞正在尝试修复。

我意识到上面的框架在 CSRF 令牌的整个往返过程中不使用 cookie;他们在下游使用 Set-Cookie,然后在上游使用其他东西(例如 X-CSRF-Token 标头),这确实关闭了漏洞。但即使使用 Set-Cookie 作为下游传输也可能具有误导性和危险性;浏览器现在会将 CSRF 令牌附加到每个请求,包括真正的恶意 XSRF 请求;充其量这会使请求比它需要的更大,最坏的情况是一些善意但被误导的服务器代码实际上可能会尝试使用它,这将是非常糟糕的。此外,由于 CSRF 令牌的实际预期接收者是客户端 Javascript,这意味着该 cookie 不能仅使用 http 来保护。因此,在 Set-Cookie 标头中向下游发送 CSRF 令牌对我来说似乎不是最理想的。

这是一个很好的问题。
更奇怪的是,OWASP 声明“不应使用 cookie 传输 CSRF 令牌”。 cheatsheetseries.owasp.org/cheatsheets/…
嗯,如果 cookie 上有 SameSite,为什么 CSRF 会成为问题?
metamatt,那么,答案是什么? (你是否重新引入了你试图修复的漏洞?为什么)。

L
Lutz Prechelt

一个很好的理由(您已经谈到)是,一旦收到 CSRF cookie,它就可以在客户端脚本中的整个应用程序中使用,用于常规表单和 AJAX POST。这在 JavaScript 繁重的应用程序中是有意义的,例如 AngularJS 使用的应用程序(使用 AngularJS 并不要求应用程序是单页应用程序,因此当状态需要在 CSRF 值的不同页面请求之间流动时,它会很有用通常不能在浏览器中持续存在)。

考虑典型应用程序中的以下场景和流程,了解您描述的每种方法的一些优缺点。这些基于 Synchronizer Token Pattern

请求正文方法

用户成功登录。服务器发出 auth cookie。用户单击以导航到表单。如果尚未为此会话生成,服务器会生成 CSRF 令牌,将其存储在用户会话中并将其输出到隐藏字段。用户提交表单。服务器检查隐藏字段是否匹配会话存储的令牌。

优点:

实施简单。

与 AJAX 一起工作。

适用于表格。

Cookie 实际上只能是 HTTP。

缺点:

所有表单都必须以 HTML 格式输出隐藏字段。

任何 AJAX POST 还必须包含该值。

页面必须提前知道它需要 CSRF 令牌,以便它可以将其包含在页面内容中,因此所有页面都必须在某处包含令牌值,这可能会使大型站点的实施变得耗时。

自定义 HTTP 标头(下游)

用户成功登录。服务器发出 auth cookie。用户单击以导航到表单。页面在浏览器中加载,然后发出 AJAX 请求以检索 CSRF 令牌。服务器生成 CSRF 令牌(如果尚未为会话生成),将其存储在用户会话中并将其输出到标头。用户提交表单(令牌通过隐藏字段发送)。服务器检查隐藏字段是否匹配会话存储的令牌。

优点:

与 AJAX 一起工作。

Cookie 只能是 HTTP。

缺点:

如果没有 AJAX 请求来获取标头值,则无法工作。

所有表单都必须动态地将值添加到其 HTML 中。

任何 AJAX POST 还必须包含该值。

页面必须首先发出 AJAX 请求才能获取 CSRF 令牌,因此每次都意味着额外的往返。

还不如简单地将令牌输出到可以保存额外请求的页面。

自定义 HTTP 标头(上游)

用户成功登录。服务器发出 auth cookie。用户单击以导航到表单。如果尚未为此会话生成,服务器会生成 CSRF 令牌,将其存储在用户会话中并将其输出到页面内容中的某处。用户通过 AJAX 提交表单(令牌通过标头发送)。服务器检查自定义标头与会话存储的令牌匹配。

优点:

与 AJAX 一起工作。

Cookie 只能是 HTTP。

缺点:

不适用于表格。

所有 AJAX POST 都必须包含标头。

自定义 HTTP 标头(上游和下游)

用户成功登录。服务器发出 auth cookie。用户单击以导航到表单。页面在浏览器中加载,然后发出 AJAX 请求以检索 CSRF 令牌。服务器生成 CSRF 令牌(如果尚未为会话生成),将其存储在用户会话中并将其输出到标头。用户通过 AJAX 提交表单(令牌通过标头发送)。服务器检查自定义标头与会话存储的令牌匹配。

优点:

与 AJAX 一起工作。

Cookie 只能是 HTTP。

缺点:

不适用于表格。

所有 AJAX POST 还必须包含该值。

页面必须首先发出 AJAX 请求才能获取 CRSF 令牌,因此每次都意味着额外的往返。

设置Cookie

用户成功登录。服务器发出 auth cookie。用户单击以导航到表单。服务器生成 CSRF 令牌,将其存储在用户会话中并将其输出到 cookie。用户通过 AJAX 或 HTML 表单提交表单。服务器检查自定义标头(或隐藏的表单字段)是否与会话存储的令牌匹配。 Cookie 可在浏览器中用于额外的 AJAX 和表单请求,而无需额外请求服务器来检索 CSRF 令牌。

优点:

实施简单。

与 AJAX 一起工作。

适用于表格。

不一定需要 AJAX 请求来获取 cookie 值。任何 HTTP 请求都可以检索它,并且可以通过 JavaScript 将其附加到所有表单/AJAX 请求中。

检索到 CSRF 令牌后,由于它存储在 cookie 中,因此无需额外请求即可重用该值。

缺点:

所有表单都必须动态地将值添加到其 HTML 中。

任何 AJAX POST 还必须包含该值。

cookie 将针对每个请求提交(即所有 GET 的图像、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。因此,这是您使用基于 cookie 的方法发现 CSRF 预防的另一个常见原因。


我不确定我是否理解如何安全地完成“发出 AJAX 请求以检索 CSRF 令牌”(“自定义标头:下游”部分中的第 4 步);由于这是一个单独的请求,服务器不知道它来自谁;它怎么知道泄露 CSRF 令牌是安全的?在我看来,如果您无法从初始页面加载中获取令牌,您就会失败(不幸的是,这使得自定义下游响应标头无法启动)。
因为伪造者没有会话cookie。他们可能有自己的会话 cookie,但由于 CSRF 令牌与会话相关联,因此他们的 CSRF 令牌与受害者的不匹配。
根据我对 CSRF 攻击的理解,伪造者确实有我的会话 cookie。好吧,他们实际上并没有看到 cookie,但他们有能力在他们的伪造请求中提供它,因为请求来自我的浏览器,而我的浏览器提供了我的会话 cookie。所以从服务器的角度来看,单独的会话cookie不能区分合法请求和伪造请求。这实际上是我们试图阻止的攻击。顺便说一句,感谢您耐心地讨论这个问题,特别是如果我对此感到困惑。
他们有能力提供 auth cookie,但他们无法读取包含 CSRF 令牌的响应。
@metamat 很抱歉死灵,但我会为那些徘徊的人做这件事。据我了解,攻击者通常无法访问响应。 CSRF 主要用于产生副作用,而不是直接收集数据。例如,CSRF 攻击脚本可能会强制特权用户提升攻击者的权限、禁用安全设置或强制登录的 paypal 用户向特定电子邮件地址发送转账。在这些情况下,攻击者都不关心响应,响应仍然发送到受害者的浏览器;只有攻击的结果。
T
Tongfa

使用 cookie 向客户端提供 CSRF 令牌不允许成功攻击,因为攻击者无法读取 cookie 的值,因此无法将其放置在服务器端 CSRF 验证要求的位置。

攻击者将能够使用请求标头中的身份验证令牌 cookie 和 CSRF cookie 向服务器发起请求。但是服务器并没有在请求标头中将 CSRF 令牌作为 cookie 来查找,而是在请求的有效负载中查找。即使攻击者知道将 CSRF 令牌放在有效负载中的哪个位置,他们也必须读取其值才能将其放在那里。但是浏览器的跨域策略会阻止从目标网站读取任何 cookie 值。

相同的逻辑不适用于身份验证令牌 cookie,因为服务器期望它在请求标头中,并且攻击者不需要做任何特殊的事情就可以将它放在那里。


当然,攻击者首先不需要读取 cookie。他们可以使用浏览器请求的 src='bank.com/transfer?to=hacker&amount=1000 在被黑网站上插入图片,并附上该网站的相关 cookie (bank.com)?
CSRF 用于在客户端验证用户,而不是像您建议的那样通常保护站点免受服务器端损害。
@developius 发送 cookie 不足以满足 CSRF 保护。 cookie 包含服务器发送的 csrf 令牌。合法客户端必须从 cookie 中读取 csrf 令牌,然后将其传递到请求中的某处,例如标头或负载中。 CSRF 保护检查 cookie 中的值是否与请求中的值匹配,否则请求被拒绝。因此,攻击者确实需要读取 cookie。
这个答案对原始发帖人的问题非常中肯,而且非常清楚。 +1 谢谢。
@Tongfa - 谢谢,这帮助我更好地理解。我是否正确假设不应将 CSRF 令牌放在标题中?它一定在身体的某个地方?
C
Community

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

在请求正文中(不是 HTTP 标头)。在自定义 HTTP 标头中,而不是 Set-Cookie。作为 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 标头携带的 cookie,作为一种在所有情况下都易于使用的方法(任何人的服务器都可以设置每个请求的 cookie 标头,无论哪种类型的数据在请求正文中)。因此,尽管有缺点,但它是框架广泛实施的最简单方法。


我可能会说很明显,这是否意味着 cookie 不能是 httponly 正确的?
仅适用于 ajax 请求(其中 JS 需要知道 csrf cookie 的值以便在第二个通道中的下一个请求中重新发送它(作为表单数据或标头))。如果会话 cookie 已经是 HttpOnly(以防止 XSS),则没有理由要求 csrf 令牌为 HttpOnly,因为如果没有关联的会话,csrf 令牌本身没有价值。
L
Lokesh

除了会话 cookie(这是一种标准),我不想使用额外的 cookie。

在构建具有许多 AJAX 请求的单页 Web 应用程序 (SPA) 时,我找到了一个适合我的解决方案。注意:我使用的是服务器端 Java 和客户端 JQuery,但没有什么神奇的东西,所以我认为这个原理可以在所有流行的编程语言中实现。

我没有额外 cookie 的解决方案很简单:

客户端

将成功登录后服务器返回的 CSRF 令牌存储在全局变量中(如果您想使用 Web 存储而不是全局,那当然可以)。指示 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);
}); 

服务器端

成功登录后,创建一个随机(且足够长)的 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);
}   

我认为您会希望 CSRF 用于登录请求。您似乎也将 CSRF 令牌用作登录会话令牌。它还可以将它们作为单独的令牌,然后您可以在任何端点上使用 CSRF,无论用户是否登录。