公开会话的 CSRF 保护令牌是不是安全?

Posted

技术标签:

【中文标题】公开会话的 CSRF 保护令牌是不是安全?【英文标题】:Is exposing a session's CSRF-protection token safe?公开会话的 CSRF 保护令牌是否安全? 【发布时间】:2010-09-13 18:25:57 【问题描述】:

Django 带有CSRF protection middleware,它会生成一个唯一的每会话令牌以在表单中使用。它扫描所有传入的POST 请求以获取正确的令牌,如果令牌丢失或无效,则拒绝该请求。

我想对一些 POST 请求使用 AJAX,但是说请求没有可用的 CSRF 令牌。这些页面没有<form> 可以挂钩的元素,我不想混淆将标记作为隐藏值插入的标记。我认为这样做的一个好方法是公开一个像 /get-csrf-token/ 这样的 vew 来返回用户的令牌,依靠浏览器的跨站点脚本规则来防止恶意站点请求它。

这是个好主意吗?是否有更好的方法来抵御 CSRF 攻击,同时仍然允许 AJAX 请求?

【问题讨论】:

【参考方案1】:

更新:以下是正确的,如果所有浏览器和插件都正确实施,则应该是正确的。不幸的是,我们现在知道它们不是,并且浏览器插件和重定向的某些组合可以允许攻击者在跨域请求中提供任意标头。不幸的是,这意味着即使是带有“X-Requested-With: XMLHttpRequest”标头的 AJAX 请求现在也必须受 CSRF 保护。结果,Django no longer exempts Ajax requests from CSRF protection.

原答案

值得一提的是,保护 AJAX 请求免受 CSRF 影响是不必要的,因为浏览器不允许跨站点 AJAX 请求。事实上,Django CSRF 中间件现在是automatically exempts AJAX requests from CSRF token scanning。

这仅在您实际检查 X-Requested-With 标头服务器端的“XMLHttpRequest”值(Django 会这样做)并且仅从 CSRF 扫描中免除真正的 AJAX 请求时才有效。

【讨论】:

这不是真的。 ajax 请求显然是与服务器(您的)通信。作为攻击者,我仍然可以通过多种不同的方法从其他任何地方创建相同的发布请求,并且没有 CSRF 保护,如果用户登录,我将成功。 @Nathan 您不能“通过多种不同的方法从其他任何地方创建相同的发布请求”,因为浏览器将拒绝执行该发布请求(除非您可以让我的域为您的 JS 提供服务,在这种情况下,我显然还有另一个洞)。您必须绕过同域限制,或者欺骗用户的浏览器执行非 AJAX 请求并欺骗 X-Requested-With 标头。无论哪种方式,如果您能够做到,那就是浏览器错误。欢迎工作演示。 @Carl Meyer:我还是不同意。我可以(例如)将用户引诱到我控制的网站,该网站以与 AJAX 请求相同的方式向服务器发布帖子 - 无需将 javascript 注入您的网站。普通的 http 帖子没有同域限制 - 我可以以任何形式发布到任何域。 cookie 随请求一起发送,而不是随页面一起发送。这仍然属于跨站点请求伪造的类别。 @Nathan 再一次,不。当然,您可以让用户的浏览器 POST 到我的域,但如果是普通的表单提交,它将不是 AJAX 请求(根据我正在检查服务器端的 X-Requested-With 标头)。并且您不能强制用户的浏览器添加该标头而不违反同域 AJAX 限制。如果可以,那是浏览器的错误。再说一遍:如果您认为这是可能的,请构建一个演示来展示它的运行情况。 @Carl Meyer - 在再次查看您的 cmets 并更仔细地查看您在答案中包含的链接之后,我现在同意您的看法。 :-) 你必须先编辑你的答案,然后才能让我改变我的投票。感谢您在这次讨论中与我相处,我学到了一些东西。【参考方案2】:

如果您知道 AJAX 请求需要 CSRF 令牌,您可以随时将其嵌入 html 中的某处;然后你可以通过 Javascript 通过遍历 DOM 找到它。这样,您仍然可以访问令牌,但不会通过 API 公开它。

换一种说法:通过 Django 的模板来完成——而不是通过 URL 调度程序。这种方式更安全。

【讨论】:

CSRF 令牌应该随着每次操作而改变。您是否建议每个操作都更新令牌?这是否意味着用户的操作必须同步(在第一个返回并给出新的 csrf 令牌之前,第二个不能开始)。 这不是最佳答案。正如 Carl Meyer 在他的回答中所说:没有必要通过在 Django 中使用令牌来保护来自 CSRF 的 AJAX 请求,因为 Django 已经以不同的方式保护 AJAX 请求。这也消除了神秘主义者的担忧。 也许这会有所帮助,即使我没有对这个关于 ajax 和 CSRF 的故事进行排序 docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax 这个来自@amirouche 的文档链接很有用。我们现在可以xhr.setRequestHeader("X-CSRFToken", csrftoken) 从 cookie $.cookie('csrftoken') 中获取令牌。由于令牌无处出现,因此更加安全。【参考方案3】:

取消,我错了。 (请参阅 cmets。) 您可以通过确保您的 JSON 遵循规范来防止利用:始终确保您返回一个对象文字作为***对象。 (我不能保证不会有进一步的漏洞利用。想象一下浏览器在其 window.onerror 事件中提供对失败代码的访问!)

您不能依赖跨站点脚本规则来保持 AJAX 响应的私密性。例如,如果您以 JSON 形式返回 CSRF 令牌,则恶意网站可以redefine the String or Array constructor 并请求资源。

bigmattyh 是正确的:您需要将标记嵌入到标记中的某个位置。或者,您可以拒绝任何 确实 具有 匹配的引荐来源网址的 POST。这样一来,只有过度使用软件防火墙的人才容易受到 CSRF 的攻击。

【讨论】:

呃,又是那个链接。至少作者在最后添加了一个注释,说明他错了。 JSON 实际上不容易受到重新定义构造函数的影响,因为当您尝试解析未修饰的 JSON 时,Javascript 解析器会引发异常。

以上是关于公开会话的 CSRF 保护令牌是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章

已经实施 csrf 保护时使用会话 id 的目的是啥?

使用会话令牌实现 CSRF 保护

Spring CSRF 令牌生命周期

Spring 安全中的条件 CSRF 保护

没有会话或数据库的安全 CSRF 保护?

绕过 XHR 的 CSRF 保护是不是安全? (导轨)